001package org.apache.commons.jcs3.auxiliary.remote;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.IOException;
023import java.rmi.UnmarshalException;
024import java.util.ArrayList;
025import java.util.Collections;
026import java.util.HashMap;
027import java.util.Map;
028import java.util.Set;
029
030import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCache;
031import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
032import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheClient;
033import org.apache.commons.jcs3.engine.CacheAdaptor;
034import org.apache.commons.jcs3.engine.CacheEventQueueFactory;
035import org.apache.commons.jcs3.engine.CacheStatus;
036import org.apache.commons.jcs3.engine.behavior.ICacheElement;
037import org.apache.commons.jcs3.engine.behavior.ICacheEventQueue;
038import org.apache.commons.jcs3.engine.behavior.ICacheServiceNonLocal;
039import org.apache.commons.jcs3.engine.stats.StatElement;
040import org.apache.commons.jcs3.engine.stats.Stats;
041import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
042import org.apache.commons.jcs3.engine.stats.behavior.IStats;
043import org.apache.commons.jcs3.log.Log;
044import org.apache.commons.jcs3.log.LogManager;
045
046/**
047 * The RemoteCacheNoWait wraps the RemoteCacheClient. The client holds a handle on the
048 * RemoteCacheService.
049 * <p>
050 * Used to queue up update requests to the underlying cache. These requests will be processed in
051 * their order of arrival via the cache event queue processor.
052 * <p>
053 * Typically errors will be handled down stream. We only need to kill the queue if an error makes it
054 * to this level from the queue. That can only happen if the queue is damaged, since the events are
055 * Processed asynchronously.
056 * <p>
057 * There is no reason to create a queue on startup if the remote is not healthy.
058 * <p>
059 * If the remote cache encounters an error it will zombie--create a balking facade for the service.
060 * The Zombie will queue up items until the connection is restored. An alternative way to accomplish
061 * the same thing would be to stop, not destroy the queue at this level. That way items would be
062 * added to the queue and then when the connection is restored, we could start the worker threads
063 * again. This is a better long term solution, but it requires some significant changes to the
064 * complicated worker queues.
065 */
066public class RemoteCacheNoWait<K, V>
067    extends AbstractAuxiliaryCache<K, V>
068{
069    /** log instance */
070    private static final Log log = LogManager.getLog( RemoteCacheNoWait.class );
071
072    /** The remote cache client */
073    private final IRemoteCacheClient<K, V> remoteCacheClient;
074
075    /** Event queue for queuing up calls like put and remove. */
076    private ICacheEventQueue<K, V> cacheEventQueue;
077
078    /** how many times get has been called. */
079    private int getCount;
080
081    /** how many times getMatching has been called. */
082    private int getMatchingCount;
083
084    /** how many times getMultiple has been called. */
085    private int getMultipleCount;
086
087    /** how many times remove has been called. */
088    private int removeCount;
089
090    /** how many times put has been called. */
091    private int putCount;
092
093    /**
094     * Constructs with the given remote cache, and fires up an event queue for asynchronous
095     * processing.
096     * <p>
097     * @param cache
098     */
099    public RemoteCacheNoWait( final IRemoteCacheClient<K, V> cache )
100    {
101        this.remoteCacheClient = cache;
102        this.cacheEventQueue = createCacheEventQueue(cache);
103
104        if ( remoteCacheClient.getStatus() == CacheStatus.ERROR )
105        {
106            cacheEventQueue.destroy();
107        }
108    }
109
110    /**
111     * Create a cache event queue from the parameters of the remote client
112     * @param client the remote client
113     */
114    private ICacheEventQueue<K, V> createCacheEventQueue( final IRemoteCacheClient<K, V> client )
115    {
116        final CacheEventQueueFactory<K, V> factory = new CacheEventQueueFactory<>();
117        return factory.createCacheEventQueue(
118            new CacheAdaptor<>( client ),
119            client.getListenerId(),
120            client.getCacheName(),
121            client.getAuxiliaryCacheAttributes().getEventQueuePoolName(),
122            client.getAuxiliaryCacheAttributes().getEventQueueType() );
123    }
124
125    /**
126     * Adds a put event to the queue.
127     * <p>
128     * @param element
129     * @throws IOException
130     */
131    @Override
132    public void update( final ICacheElement<K, V> element )
133        throws IOException
134    {
135        putCount++;
136        try
137        {
138            cacheEventQueue.addPutEvent( element );
139        }
140        catch ( final IOException e )
141        {
142            log.error( "Problem adding putEvent to queue.", e );
143            cacheEventQueue.destroy();
144            throw e;
145        }
146    }
147
148    /**
149     * Synchronously reads from the remote cache.
150     * <p>
151     * @param key
152     * @return element from the remote cache, or null if not present
153     * @throws IOException
154     */
155    @Override
156    public ICacheElement<K, V> get( final K key )
157        throws IOException
158    {
159        getCount++;
160        try
161        {
162            return remoteCacheClient.get( key );
163        }
164        catch ( final UnmarshalException ue )
165        {
166            log.debug( "Retrying the get owing to UnmarshalException." );
167
168            try
169            {
170                return remoteCacheClient.get( key );
171            }
172            catch ( final IOException ex )
173            {
174                log.info( "Failed in retrying the get for the second time. ", ex );
175            }
176        }
177        catch ( final IOException ex )
178        {
179            // We don't want to destroy the queue on a get failure.
180            // The RemoteCache will Zombie and queue.
181            // Since get does not use the queue, I don't want to kill the queue.
182            throw ex;
183        }
184
185        return null;
186    }
187
188    /**
189     * @param pattern
190     * @return Map
191     * @throws IOException
192     *
193     */
194    @Override
195    public Map<K, ICacheElement<K, V>> getMatching( final String pattern )
196        throws IOException
197    {
198        getMatchingCount++;
199        try
200        {
201            return remoteCacheClient.getMatching( pattern );
202        }
203        catch ( final UnmarshalException ue )
204        {
205            log.debug( "Retrying the getMatching owing to UnmarshalException." );
206
207            try
208            {
209                return remoteCacheClient.getMatching( pattern );
210            }
211            catch ( final IOException ex )
212            {
213                log.info( "Failed in retrying the getMatching for the second time.", ex );
214            }
215        }
216        catch ( final IOException ex )
217        {
218            // We don't want to destroy the queue on a get failure.
219            // The RemoteCache will Zombie and queue.
220            // Since get does not use the queue, I don't want to kill the queue.
221            throw ex;
222        }
223
224        return Collections.emptyMap();
225    }
226
227    /**
228     * Gets multiple items from the cache based on the given set of keys. Sends the getMultiple
229     * request on to the server rather than looping through the requested keys.
230     * <p>
231     * @param keys
232     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
233     *         data in cache for any of these keys
234     * @throws IOException
235     */
236    @Override
237    public Map<K, ICacheElement<K, V>> getMultiple( final Set<K> keys )
238        throws IOException
239    {
240        getMultipleCount++;
241        try
242        {
243            return remoteCacheClient.getMultiple( keys );
244        }
245        catch ( final UnmarshalException ue )
246        {
247            log.debug( "Retrying the getMultiple owing to UnmarshalException..." );
248
249            try
250            {
251                return remoteCacheClient.getMultiple( keys );
252            }
253            catch ( final IOException ex )
254            {
255                log.info( "Failed in retrying the getMultiple for the second time.", ex );
256            }
257        }
258        catch ( final IOException ex )
259        {
260            // We don't want to destroy the queue on a get failure.
261            // The RemoteCache will Zombie and queue.
262            // Since get does not use the queue, I don't want to kill the queue.
263            throw ex;
264        }
265
266        return new HashMap<>();
267    }
268
269    /**
270     * Return the keys in this cache.
271     * <p>
272     * @see org.apache.commons.jcs3.auxiliary.AuxiliaryCache#getKeySet()
273     */
274    @Override
275    public Set<K> getKeySet() throws IOException
276    {
277        return remoteCacheClient.getKeySet();
278    }
279
280    /**
281     * Adds a remove request to the remote cache.
282     * <p>
283     * @param key
284     * @return if this was successful
285     * @throws IOException
286     */
287    @Override
288    public boolean remove( final K key )
289        throws IOException
290    {
291        removeCount++;
292        try
293        {
294            cacheEventQueue.addRemoveEvent( key );
295        }
296        catch ( final IOException e )
297        {
298            log.error( "Problem adding RemoveEvent to queue.", e );
299            cacheEventQueue.destroy();
300            throw e;
301        }
302        return false;
303    }
304
305    /**
306     * Adds a removeAll request to the remote cache.
307     * <p>
308     * @throws IOException
309     */
310    @Override
311    public void removeAll()
312        throws IOException
313    {
314        try
315        {
316            cacheEventQueue.addRemoveAllEvent();
317        }
318        catch ( final IOException e )
319        {
320            log.error( "Problem adding RemoveAllEvent to queue.", e );
321            cacheEventQueue.destroy();
322            throw e;
323        }
324    }
325
326    /** Adds a dispose request to the remote cache. */
327    @Override
328    public void dispose()
329    {
330        try
331        {
332            cacheEventQueue.addDisposeEvent();
333        }
334        catch ( final IOException e )
335        {
336            log.error( "Problem adding DisposeEvent to queue.", e );
337            cacheEventQueue.destroy();
338        }
339    }
340
341    /**
342     * No remote invocation.
343     * <p>
344     * @return The size value
345     */
346    @Override
347    public int getSize()
348    {
349        return remoteCacheClient.getSize();
350    }
351
352    /**
353     * No remote invocation.
354     * <p>
355     * @return The cacheType value
356     */
357    @Override
358    public CacheType getCacheType()
359    {
360        return CacheType.REMOTE_CACHE;
361    }
362
363    /**
364     * Returns the async cache status. An error status indicates either the remote connection is not
365     * available, or the asyn queue has been unexpectedly destroyed. No remote invocation.
366     * <p>
367     * @return The status value
368     */
369    @Override
370    public CacheStatus getStatus()
371    {
372        return cacheEventQueue.isWorking() ? remoteCacheClient.getStatus() : CacheStatus.ERROR;
373    }
374
375    /**
376     * Gets the cacheName attribute of the RemoteCacheNoWait object
377     * <p>
378     * @return The cacheName value
379     */
380    @Override
381    public String getCacheName()
382    {
383        return remoteCacheClient.getCacheName();
384    }
385
386    /**
387     * Replaces the remote cache service handle with the given handle and reset the event queue by
388     * starting up a new instance.
389     * <p>
390     * @param remote
391     */
392    public void fixCache( final ICacheServiceNonLocal<?, ?> remote )
393    {
394        remoteCacheClient.fixCache( remote );
395        resetEventQ();
396    }
397
398    /**
399     * Resets the event q by first destroying the existing one and starting up new one.
400     * <p>
401     * There may be no good reason to kill the existing queue. We will sometimes need to set a new
402     * listener id, so we should create a new queue. We should let the old queue drain. If we were
403     * Connected to the failover, it would be best to finish sending items.
404     */
405    public void resetEventQ()
406    {
407        final ICacheEventQueue<K, V> previousQueue = cacheEventQueue;
408
409        this.cacheEventQueue = createCacheEventQueue(this.remoteCacheClient);
410
411        if ( previousQueue.isWorking() )
412        {
413            // we don't expect anything, it would have all gone to the zombie
414            log.info( "resetEventQ, previous queue has [{0}] items queued up.",
415                    previousQueue::size);
416            previousQueue.destroy();
417        }
418    }
419
420    /**
421     * This is temporary. It allows the manager to get the lister.
422     * <p>
423     * @return the instance of the remote cache client used by this object
424     */
425    protected IRemoteCacheClient<K, V> getRemoteCache()
426    {
427        return remoteCacheClient;
428    }
429
430    /**
431     * @return Returns the AuxiliaryCacheAttributes.
432     */
433    @Override
434    public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
435    {
436        return remoteCacheClient.getAuxiliaryCacheAttributes();
437    }
438
439    /**
440     * This is for testing only. It allows you to take a look at the event queue.
441     * <p>
442     * @return ICacheEventQueue
443     */
444    protected ICacheEventQueue<K, V> getCacheEventQueue()
445    {
446        return this.cacheEventQueue;
447    }
448
449    /**
450     * Returns the stats and the cache.toString().
451     * <p>
452     * @see Object#toString()
453     */
454    @Override
455    public String toString()
456    {
457        return getStats() + "\n" + remoteCacheClient.toString();
458    }
459
460    /**
461     * Returns the statistics in String form.
462     * <p>
463     * @return String
464     */
465    @Override
466    public String getStats()
467    {
468        return getStatistics().toString();
469    }
470
471    /**
472     * @return statistics about this communication
473     */
474    @Override
475    public IStats getStatistics()
476    {
477        final IStats stats = new Stats();
478        stats.setTypeName( "Remote Cache No Wait" );
479
480        final ArrayList<IStatElement<?>> elems = new ArrayList<>();
481
482        elems.add(new StatElement<>( "Status", getStatus() ) );
483
484        // get the stats from the cache queue too
485        final IStats cStats = this.remoteCacheClient.getStatistics();
486        if ( cStats != null )
487        {
488            elems.addAll(cStats.getStatElements());
489        }
490
491        // get the stats from the event queue too
492        final IStats eqStats = this.cacheEventQueue.getStatistics();
493        elems.addAll(eqStats.getStatElements());
494
495        elems.add(new StatElement<>( "Get Count", Integer.valueOf(this.getCount) ) );
496        elems.add(new StatElement<>( "GetMatching Count", Integer.valueOf(this.getMatchingCount) ) );
497        elems.add(new StatElement<>( "GetMultiple Count", Integer.valueOf(this.getMultipleCount) ) );
498        elems.add(new StatElement<>( "Remove Count", Integer.valueOf(this.removeCount) ) );
499        elems.add(new StatElement<>( "Put Count", Integer.valueOf(this.putCount) ) );
500
501        stats.setStatElements( elems );
502
503        return stats;
504    }
505
506    /**
507     * this won't be called since we don't do ICache logging here.
508     * <p>
509     * @return String
510     */
511    @Override
512    public String getEventLoggingExtraInfo()
513    {
514        return "Remote Cache No Wait";
515    }
516}