001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 2011-2013 Pentaho and others
008// All Rights Reserved.
009*/
010package mondrian.rolap.agg;
011
012import mondrian.olap.MondrianProperties;
013import mondrian.resource.MondrianResource;
014import mondrian.spi.*;
015import mondrian.util.ClassResolver;
016import mondrian.util.ServiceDiscovery;
017
018import org.apache.log4j.Logger;
019
020import java.util.*;
021
022/**
023 * Utility class to interact with the {@link SegmentCache}.
024 *
025 * @author LBoudreau
026 * @see SegmentCache
027 */
028public final class SegmentCacheWorker {
029
030    private static final Logger LOGGER =
031        Logger.getLogger(SegmentCacheWorker.class);
032
033    final SegmentCache cache;
034    private final Thread cacheMgrThread;
035    private final boolean supportsRichIndex;
036
037    /**
038     * Creates a worker.
039     *
040     * @param cache Cache managed by this worker
041     * @param cacheMgrThread Thread that the cache manager actor is running on,
042     *                       and which therefore should not be used for
043     *                       potentially long-running calls this this cache.
044     *                       Pass null if methods can be called from any thread.
045     */
046    public SegmentCacheWorker(SegmentCache cache, Thread cacheMgrThread) {
047        this.cache = cache;
048        this.cacheMgrThread = cacheMgrThread;
049
050        // no need to call checkThread(): supportsRichIndex is a fast call
051        this.supportsRichIndex = cache.supportsRichIndex();
052
053        LOGGER.debug(
054            "Segment cache initialized: "
055            + cache.getClass().getName());
056    }
057
058    /**
059     * Instantiates a cache. Returns null if there is no external cache defined.
060     *
061     * @return Cache
062     */
063    public static List<SegmentCache> initCache() {
064        final List<SegmentCache> caches =
065            new ArrayList<SegmentCache>();
066        // First try to get the segmentcache impl class from
067        // mondrian properties.
068        final String cacheName =
069            MondrianProperties.instance().SegmentCache.get();
070        if (cacheName != null) {
071            caches.add(instantiateCache(cacheName));
072        }
073
074        // There was no property set. Let's look for Java services.
075        final List<Class<SegmentCache>> implementors =
076            ServiceDiscovery.forClass(SegmentCache.class).getImplementor();
077        if (implementors.size() > 0) {
078            // The contract is to use the first implementation found.
079            SegmentCache cache =
080                instantiateCache(implementors.get(0).getName());
081            if (cache != null) {
082                caches.add(cache);
083            }
084        }
085
086        // Check the SegmentCacheInjector
087        // People might have sent instances into this thing.
088        caches.addAll(SegmentCache.SegmentCacheInjector.getCaches());
089
090        // Done.
091        return caches;
092    }
093
094    /**
095     * Instantiates a cache, given the name of the cache class.
096     *
097     * @param cacheName Name of class that implements the
098     *     {@link mondrian.spi.SegmentCache} SPI
099     *
100     * @return Cache instance, or null on error
101     */
102    private static SegmentCache instantiateCache(String cacheName) {
103        try {
104            LOGGER.debug("Starting cache instance: " + cacheName);
105            return ClassResolver.INSTANCE.instantiateSafe(cacheName);
106        } catch (ClassCastException e) {
107            throw MondrianResource.instance()
108                .SegmentCacheIsNotImplementingInterface.ex();
109        } catch (Exception e) {
110            LOGGER.error(
111                MondrianResource.instance()
112                    .SegmentCacheFailedToInstanciate.baseMessage,
113                e);
114            throw MondrianResource.instance()
115                .SegmentCacheFailedToInstanciate.ex(e);
116        }
117    }
118
119    /**
120     * Returns a segment body corresponding to a header.
121     *
122     * <p>If no cache is configured or there is an error while
123     * querying the cache, null is returned none the less.
124     *
125     * @param header Header to search.
126     * @return Either a segment body object or null if there
127     * was no cache configured or no segment could be found
128     * for the passed header.
129     */
130    public SegmentBody get(SegmentHeader header) {
131        checkThread();
132        try {
133            return cache.get(header);
134        } catch (Throwable t) {
135            LOGGER.error(
136                MondrianResource.instance()
137                    .SegmentCacheFailedToLoadSegment
138                    .baseMessage,
139                t);
140            throw MondrianResource.instance()
141                .SegmentCacheFailedToLoadSegment.ex(t);
142        }
143    }
144
145    /**
146     * Places a segment in the cache. Returns true or false
147     * if the operation succeeds.
148     *
149     * @param header A header to search for in the segment cache.
150     * @param body The segment body to cache.
151     */
152    public void put(SegmentHeader header, SegmentBody body) {
153        checkThread();
154        try {
155            final boolean result = cache.put(header, body);
156            if (!result) {
157                LOGGER.error(
158                    MondrianResource.instance()
159                        .SegmentCacheFailedToSaveSegment
160                        .baseMessage);
161                throw MondrianResource.instance()
162                    .SegmentCacheFailedToSaveSegment.ex();
163            }
164        } catch (Throwable t) {
165            LOGGER.error(
166                MondrianResource.instance()
167                    .SegmentCacheFailedToSaveSegment
168                    .baseMessage,
169                t);
170            throw MondrianResource.instance()
171                .SegmentCacheFailedToSaveSegment.ex(t);
172        }
173    }
174
175    /**
176     * Removes a segment from the cache.
177     *
178     * @param header A header to remove in the segment cache.
179     * @return Whether a segment was removed
180     */
181    public boolean remove(SegmentHeader header) {
182        checkThread();
183        try {
184            return cache.remove(header);
185        } catch (Throwable t) {
186            LOGGER.error(
187                MondrianResource.instance()
188                    .SegmentCacheFailedToDeleteSegment
189                    .baseMessage,
190                t);
191            throw MondrianResource.instance()
192                .SegmentCacheFailedToDeleteSegment.ex(t);
193        }
194    }
195
196    /**
197     * Returns a list of segments present in the cache.
198     *
199     * @return List of headers in the cache
200     */
201    public List<SegmentHeader> getSegmentHeaders() {
202        checkThread();
203        try {
204            return cache.getSegmentHeaders();
205        } catch (Throwable t) {
206            LOGGER.error("Failed to get a list of segment headers.", t);
207            throw MondrianResource.instance()
208                .SegmentCacheFailedToScanSegments.ex(t);
209        }
210    }
211
212    public boolean supportsRichIndex() {
213        return supportsRichIndex;
214    }
215
216    public void shutdown() {
217        checkThread();
218        cache.tearDown();
219    }
220
221    private void checkThread() {
222        assert cacheMgrThread != Thread.currentThread()
223            : "this method is potentially slow; you should not call it from "
224            + "the cache manager thread, " + cacheMgrThread;
225    }
226}
227
228// End SegmentCacheWorker.java