001package org.apache.commons.jcs3.auxiliary.lateral;
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.util.ArrayList;
024import java.util.Arrays;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.List;
028import java.util.Map;
029import java.util.Map.Entry;
030import java.util.Objects;
031import java.util.Set;
032import java.util.concurrent.ConcurrentHashMap;
033import java.util.concurrent.atomic.AtomicBoolean;
034import java.util.stream.Collectors;
035
036import org.apache.commons.jcs3.auxiliary.AbstractAuxiliaryCache;
037import org.apache.commons.jcs3.auxiliary.lateral.behavior.ILateralCacheAttributes;
038import org.apache.commons.jcs3.auxiliary.lateral.behavior.ILateralCacheListener;
039import org.apache.commons.jcs3.engine.CacheStatus;
040import org.apache.commons.jcs3.engine.behavior.ICacheElement;
041import org.apache.commons.jcs3.engine.stats.StatElement;
042import org.apache.commons.jcs3.engine.stats.Stats;
043import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
044import org.apache.commons.jcs3.engine.stats.behavior.IStats;
045import org.apache.commons.jcs3.log.Log;
046import org.apache.commons.jcs3.log.LogManager;
047
048/**
049 * Used to provide access to multiple services under nowait protection. Composite factory should
050 * construct LateralCacheNoWaitFacade to give to the composite cache out of caches it constructs
051 * from the varies manager to lateral services. Perhaps the lateralcache factory should be able to
052 * do this.
053 */
054public class LateralCacheNoWaitFacade<K, V>
055    extends AbstractAuxiliaryCache<K, V>
056{
057    /** The logger */
058    private static final Log log = LogManager.getLog( LateralCacheNoWaitFacade.class );
059
060    /**
061     * The queuing facade to the client.
062     * @deprecated Should not have been public in the first place
063     */
064    @Deprecated
065    public LateralCacheNoWait<K, V>[] noWaits;
066
067    /**
068     * The queuing facade to the client.
069     */
070    private final ConcurrentHashMap<String, LateralCacheNoWait<K, V>> noWaitMap;
071
072    /** The region name */
073    private final String cacheName;
074
075    /** A cache listener */
076    private ILateralCacheListener<K, V> listener;
077
078    /** User configurable attributes. */
079    private final ILateralCacheAttributes lateralCacheAttributes;
080
081    /** Disposed state of this facade */
082    private final AtomicBoolean disposed = new AtomicBoolean(false);
083
084    /**
085     * Constructs with the given lateral cache, and fires events to any listeners.
086     * <p>
087     * @param listener the cache listener
088     * @param noWaits the array of noWaits
089     * @param cattr the configuration
090     *
091     * @deprecated Use list constructor
092     */
093    @Deprecated
094    public LateralCacheNoWaitFacade(final ILateralCacheListener<K, V> listener, final LateralCacheNoWait<K, V>[] noWaits, final ILateralCacheAttributes cattr )
095    {
096        this(listener, Arrays.asList(noWaits), cattr);
097    }
098
099    /**
100     * Constructs with the given lateral cache, and fires events to any listeners.
101     * <p>
102     * @param listener the cache listener
103     * @param noWaits the list of noWaits
104     * @param cattr the configuration
105     * @since 3.1
106     */
107    @SuppressWarnings("unchecked") // No generic arrays in java
108    public LateralCacheNoWaitFacade(final ILateralCacheListener<K, V> listener,
109            final List<LateralCacheNoWait<K, V>> noWaits, final ILateralCacheAttributes cattr )
110    {
111        log.debug( "CONSTRUCTING NO WAIT FACADE" );
112        this.listener = listener;
113        this.noWaits = noWaits.toArray(new LateralCacheNoWait[0]);
114        this.noWaitMap = new ConcurrentHashMap<>();
115        noWaits.forEach(noWait -> noWaitMap.put(noWait.getIdentityKey(), noWait));
116        this.cacheName = cattr.getCacheName();
117        this.lateralCacheAttributes = cattr;
118    }
119
120    /**
121     * Return the size of the no wait list (for testing)
122     *
123     * @return the noWait list size.
124     * @since 3.1
125     */
126    protected int getNoWaitSize()
127    {
128        return noWaitMap.size();
129    }
130
131    /**
132     * Tells you if the no wait is in the list or not.
133     * <p>
134     * @param noWait
135     * @return true if the noWait is in the list.
136     */
137    public boolean containsNoWait( final LateralCacheNoWait<K, V> noWait )
138    {
139        return containsNoWait(noWait.getIdentityKey());
140    }
141
142    /**
143     * Tells you if the no wait is in the list or not by checking for its
144     * identifying key
145     * <p>
146     * @param tcpServer the identifying key
147     * @return true if the noWait is in the list.
148     * @since 3.1
149     */
150    public boolean containsNoWait(final String tcpServer)
151    {
152        return noWaitMap.containsKey(tcpServer);
153    }
154
155    /**
156     * Adds a no wait to the list if it isn't already in the list.
157     * <p>
158     * @param noWait
159     * @return true if it wasn't already contained
160     */
161    @SuppressWarnings("unchecked") // No generic arrays in Java
162    public synchronized boolean addNoWait( final LateralCacheNoWait<K, V> noWait )
163    {
164        if ( noWait == null )
165        {
166            return false;
167        }
168
169        final LateralCacheNoWait<K,V> added =
170                noWaitMap.putIfAbsent(noWait.getIdentityKey(), noWait);
171
172        if (added != null)
173        {
174            log.debug( "No Wait already contained, [{0}]", noWait );
175            return false;
176        }
177
178        noWaits = noWaitMap.values().toArray(new LateralCacheNoWait[0]);
179
180        return true;
181    }
182
183    /**
184     * Removes a no wait from the list if it is already there.
185     * <p>
186     * @param noWait
187     * @return true if it was already in the array
188     */
189    public synchronized boolean removeNoWait( final LateralCacheNoWait<K, V> noWait )
190    {
191        if (noWait == null)
192        {
193            return false;
194        }
195
196        return removeNoWait(noWait.getIdentityKey());
197    }
198
199    /**
200     * Removes a no wait from the list if it is already there by its
201     * identifying key
202     * <p>
203     * @param tcpServer the identifying key.
204     * @return true if it was already in the array
205     * @since 3.1
206     */
207    @SuppressWarnings("unchecked") // No generic arrays in java
208    public synchronized boolean removeNoWait(final String tcpServer)
209    {
210        if (tcpServer == null)
211        {
212            return false;
213        }
214
215        final LateralCacheNoWait<K,V> contained = noWaitMap.remove(tcpServer);
216        noWaits = noWaitMap.values().toArray(new LateralCacheNoWait[0]);
217
218        if (contained != null)
219        {
220            contained.dispose();
221        }
222
223        return contained != null;
224    }
225
226    /**
227     * Update the cache element in all lateral caches
228     * @param ce the cache element
229     * @throws IOException
230     */
231    @Override
232    public void update( final ICacheElement<K, V> ce )
233        throws IOException
234    {
235        log.debug("updating through lateral cache facade, noWaits.length = {0}",
236                noWaitMap::size);
237
238        for (final LateralCacheNoWait<K, V> nw : noWaitMap.values())
239        {
240            nw.update( ce );
241        }
242    }
243
244    /**
245     * Synchronously reads from the lateral cache.
246     * <p>
247     * @param key
248     * @return ICacheElement
249     */
250    @Override
251    public ICacheElement<K, V> get( final K key )
252    {
253        return noWaitMap.values().stream()
254            .map(nw -> nw.get(key))
255            .filter(Objects::nonNull)
256            .findFirst()
257            .orElse(null);
258    }
259
260    /**
261     * Gets multiple items from the cache based on the given set of keys.
262     * <p>
263     * @param keys
264     * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
265     *         data in cache for any of these keys
266     */
267    @Override
268    public Map<K, ICacheElement<K, V>> getMultiple(final Set<K> keys)
269    {
270        if (keys != null && !keys.isEmpty())
271        {
272            return keys.stream()
273                .collect(Collectors.toMap(
274                        key -> key,
275                        this::get)).entrySet().stream()
276                    .filter(entry -> entry.getValue() != null)
277                    .collect(Collectors.toMap(
278                            Entry::getKey,
279                            Entry::getValue));
280        }
281
282        return new HashMap<>();
283    }
284
285    /**
286     * Synchronously reads from the lateral cache. Get a response from each! This will be slow.
287     * Merge them.
288     * <p>
289     * @param pattern
290     * @return ICacheElement
291     */
292    @Override
293    public Map<K, ICacheElement<K, V>> getMatching(final String pattern)
294    {
295        return noWaitMap.values().stream()
296                .flatMap(nw -> nw.getMatching(pattern).entrySet().stream())
297                .collect(Collectors.toMap(
298                        Entry::getKey,
299                        Entry::getValue));
300    }
301
302    /**
303     * Return the keys in this cache.
304     * <p>
305     * @see org.apache.commons.jcs3.auxiliary.AuxiliaryCache#getKeySet()
306     */
307    @Override
308    public Set<K> getKeySet() throws IOException
309    {
310        final HashSet<K> allKeys = new HashSet<>();
311        for (final LateralCacheNoWait<K, V> nw : noWaitMap.values())
312        {
313            final Set<K> keys = nw.getKeySet();
314            if (keys != null)
315            {
316                allKeys.addAll(keys);
317            }
318        }
319        return allKeys;
320    }
321
322    /**
323     * Adds a remove request to the lateral cache.
324     * <p>
325     * @param key
326     * @return always false.
327     */
328    @Override
329    public boolean remove( final K key )
330    {
331        noWaitMap.values().forEach(nw -> nw.remove( key ));
332        return false;
333    }
334
335    /**
336     * Adds a removeAll request to the lateral cache.
337     */
338    @Override
339    public void removeAll()
340    {
341        noWaitMap.values().forEach(LateralCacheNoWait::removeAll);
342    }
343
344    /** Adds a dispose request to the lateral cache. */
345    @Override
346    public void dispose()
347    {
348        if (disposed.compareAndSet(false, true))
349        {
350            if ( listener != null )
351            {
352                listener.dispose();
353                listener = null;
354            }
355
356            noWaitMap.values().forEach(LateralCacheNoWait::dispose);
357            noWaitMap.clear();
358        }
359    }
360
361    /**
362     * No lateral invocation.
363     * @return The size value
364     */
365    @Override
366    public int getSize()
367    {
368        return 0;
369        //cache.getSize();
370    }
371
372    /**
373     * Gets the cacheType attribute of the LateralCacheNoWaitFacade object.
374     * <p>
375     * @return The cacheType value
376     */
377    @Override
378    public CacheType getCacheType()
379    {
380        return CacheType.LATERAL_CACHE;
381    }
382
383    /**
384     * Gets the cacheName attribute of the LateralCacheNoWaitFacade object.
385     * <p>
386     * @return The cacheName value
387     */
388    @Override
389    public String getCacheName()
390    {
391        return cacheName;
392    }
393
394    /**
395     * Gets the status attribute of the LateralCacheNoWaitFacade object
396     * @return The status value
397     */
398    @Override
399    public CacheStatus getStatus()
400    {
401        if (disposed.get())
402        {
403            return CacheStatus.DISPOSED;
404        }
405
406        if (noWaitMap.isEmpty() || listener != null)
407        {
408            return CacheStatus.ALIVE;
409        }
410
411        final List<CacheStatus> statii = noWaitMap.values().stream()
412                .map(LateralCacheNoWait::getStatus)
413                .collect(Collectors.toList());
414
415        // It's alive if ANY of its nowaits is alive
416        if (statii.contains(CacheStatus.ALIVE))
417        {
418            return CacheStatus.ALIVE;
419        }
420        // It's alive if ANY of its nowaits is in error, but
421        // none are alive, then it's in error
422        if (statii.contains(CacheStatus.ERROR))
423        {
424            return CacheStatus.ERROR;
425        }
426
427        // Otherwise, it's been disposed, since it's the only status left
428        return CacheStatus.DISPOSED;
429    }
430
431    /**
432     * @return Returns the AuxiliaryCacheAttributes.
433     */
434    @Override
435    public ILateralCacheAttributes getAuxiliaryCacheAttributes()
436    {
437        return this.lateralCacheAttributes;
438    }
439
440    /**
441     * @return "LateralCacheNoWaitFacade: " + cacheName;
442     */
443    @Override
444    public String toString()
445    {
446        return "LateralCacheNoWaitFacade: " + cacheName;
447    }
448
449    /**
450     * this won't be called since we don't do ICache logging here.
451     * <p>
452     * @return String
453     */
454    @Override
455    public String getEventLoggingExtraInfo()
456    {
457        return "Lateral Cache No Wait";
458    }
459
460    /**
461     * getStats
462     * @return String
463     */
464    @Override
465    public String getStats()
466    {
467        return getStatistics().toString();
468    }
469
470    /**
471     * @return IStats
472     */
473    @Override
474    public IStats getStatistics()
475    {
476        final IStats stats = new Stats();
477        stats.setTypeName( "Lateral Cache No Wait Facade" );
478
479        final ArrayList<IStatElement<?>> elems = new ArrayList<>();
480
481        if (noWaitMap != null)
482        {
483            elems.add(new StatElement<>("Number of No Waits", Integer.valueOf(noWaitMap.size())));
484
485            elems.addAll(noWaitMap.values().stream()
486                    .flatMap(lcnw -> lcnw.getStatistics().getStatElements().stream())
487                    .collect(Collectors.toList()));
488        }
489
490        stats.setStatElements( elems );
491
492        return stats;
493    }
494}