001package org.apache.commons.jcs3.engine.control;
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.util.ArrayList;
023import java.util.List;
024import java.util.Properties;
025
026import org.apache.commons.jcs3.auxiliary.AuxiliaryCache;
027import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheAttributes;
028import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheConfigurator;
029import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheFactory;
030import org.apache.commons.jcs3.engine.behavior.ICache;
031import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
032import org.apache.commons.jcs3.engine.behavior.IElementAttributes;
033import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
034import org.apache.commons.jcs3.engine.behavior.IRequireScheduler;
035import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
036import org.apache.commons.jcs3.engine.match.KeyMatcherPatternImpl;
037import org.apache.commons.jcs3.engine.match.behavior.IKeyMatcher;
038import org.apache.commons.jcs3.log.Log;
039import org.apache.commons.jcs3.log.LogManager;
040import org.apache.commons.jcs3.utils.config.OptionConverter;
041import org.apache.commons.jcs3.utils.config.PropertySetter;
042
043/**
044 * This class configures JCS based on a properties object.
045 * <p>
046 * This class is based on the log4j class org.apache.log4j.PropertyConfigurator which was made by:
047 * "Luke Blanshard" &lt;Luke@quiq.com&gt;"Mark DONSZELMANN" &lt;Mark.Donszelmann@cern.ch&gt;"Anders Kristensen"
048 * &lt;akristensen@dynamicsoft.com&gt;
049 */
050public class CompositeCacheConfigurator
051{
052    /** The logger */
053    private static final Log log = LogManager.getLog( CompositeCacheConfigurator.class );
054
055    /** The prefix of relevant system properties */
056    protected static final String SYSTEM_PROPERTY_KEY_PREFIX = "jcs";
057
058    /** normal region prefix */
059    protected static final String REGION_PREFIX = "jcs.region.";
060
061    /** system region prefix. might not be used */
062    protected static final String SYSTEM_REGION_PREFIX = "jcs.system.";
063
064    /** auxiliary prefix */
065    protected static final String AUXILIARY_PREFIX = "jcs.auxiliary.";
066
067    /** .attributes */
068    protected static final String ATTRIBUTE_PREFIX = ".attributes";
069
070    /** .cacheattributes */
071    protected static final String CACHE_ATTRIBUTE_PREFIX = ".cacheattributes";
072
073    /** .elementattributes */
074    protected static final String ELEMENT_ATTRIBUTE_PREFIX = ".elementattributes";
075
076    /**
077     * jcs.auxiliary.NAME.keymatcher=CLASSNAME
078     * <p>
079     * jcs.auxiliary.NAME.keymatcher.attributes.CUSTOMPROPERTY=VALUE
080     */
081    public static final String KEY_MATCHER_PREFIX = ".keymatcher";
082
083    /**
084     * Constructor for the CompositeCacheConfigurator object
085     */
086    public CompositeCacheConfigurator()
087    {
088        // empty
089    }
090
091    /**
092     * Create caches used internally. System status gives them creation priority.
093     *<p>
094     * @param props Configuration properties
095     * @param ccm Cache hub
096     */
097    protected void parseSystemRegions( final Properties props, final CompositeCacheManager ccm )
098    {
099        for (final String key : props.stringPropertyNames() )
100        {
101            if ( key.startsWith( SYSTEM_REGION_PREFIX ) && key.indexOf( "attributes" ) == -1 )
102            {
103                final String regionName = key.substring( SYSTEM_REGION_PREFIX.length() );
104                final String auxiliaries = OptionConverter.findAndSubst( key, props );
105                final ICache<?, ?> cache;
106                synchronized ( regionName )
107                {
108                    cache = parseRegion( props, ccm, regionName, auxiliaries, null, SYSTEM_REGION_PREFIX );
109                }
110                ccm.addCache( regionName, cache );
111            }
112        }
113    }
114
115    /**
116     * Parse region elements.
117     *<p>
118     * @param props Configuration properties
119     * @param ccm Cache hub
120     */
121    protected void parseRegions( final Properties props, final CompositeCacheManager ccm )
122    {
123        final List<String> regionNames = new ArrayList<>();
124
125        for (final String key : props.stringPropertyNames() )
126        {
127            if ( key.startsWith( REGION_PREFIX ) && key.indexOf( "attributes" ) == -1 )
128            {
129                final String regionName = key.substring( REGION_PREFIX.length() );
130                regionNames.add( regionName );
131                final String auxiliaries = OptionConverter.findAndSubst( key, props );
132                final ICache<?, ?> cache;
133                synchronized ( regionName )
134                {
135                    cache = parseRegion( props, ccm, regionName, auxiliaries );
136                }
137                ccm.addCache( regionName, cache );
138            }
139        }
140
141        log.info( "Parsed regions {0}", regionNames );
142    }
143
144    /**
145     * Create cache region.
146     *<p>
147     * @param props Configuration properties
148     * @param ccm Cache hub
149     * @param regName Name of the cache region
150     * @param auxiliaries Comma separated list of auxiliaries
151     *
152     * @return CompositeCache
153     */
154    protected <K, V> CompositeCache<K, V> parseRegion(
155            final Properties props, final CompositeCacheManager ccm, final String regName, final String auxiliaries )
156    {
157        return parseRegion( props, ccm, regName, auxiliaries, null, REGION_PREFIX );
158    }
159
160    /**
161     * Get all the properties for a region and configure its cache.
162     * <p>
163     * This method tells the other parse method the name of the region prefix.
164     *<p>
165     * @param props Configuration properties
166     * @param ccm Cache hub
167     * @param regName Name of the cache region
168     * @param auxiliaries Comma separated list of auxiliaries
169     * @param cca Cache configuration
170     *
171     * @return CompositeCache
172     */
173    protected <K, V> CompositeCache<K, V> parseRegion(
174            final Properties props, final CompositeCacheManager ccm, final String regName, final String auxiliaries,
175            final ICompositeCacheAttributes cca )
176    {
177        return parseRegion( props, ccm, regName, auxiliaries, cca, REGION_PREFIX );
178    }
179
180    /**
181     * Get all the properties for a region and configure its cache.
182     *<p>
183     * @param props Configuration properties
184     * @param ccm Cache hub
185     * @param regName Name of the cache region
186     * @param auxiliaries Comma separated list of auxiliaries
187     * @param cca Cache configuration
188     * @param regionPrefix Prefix for the region
189     *
190     * @return CompositeCache
191     */
192    protected <K, V> CompositeCache<K, V> parseRegion(
193            final Properties props, final CompositeCacheManager ccm, final String regName, final String auxiliaries,
194            final ICompositeCacheAttributes cca, final String regionPrefix )
195    {
196        // First, create or get the cache and element attributes, and create
197        // the cache.
198        final IElementAttributes ea = parseElementAttributes( props, regName,
199                ccm.getDefaultElementAttributes(), regionPrefix );
200
201        final ICompositeCacheAttributes instantiationCca = cca == null
202                ? parseCompositeCacheAttributes(props, regName, ccm.getDefaultCacheAttributes(), regionPrefix)
203                : cca;
204        final CompositeCache<K, V> cache = newCache(instantiationCca, ea);
205
206        // Inject cache manager
207        cache.setCompositeCacheManager(ccm);
208
209        // Inject scheduler service
210        cache.setScheduledExecutorService(ccm.getScheduledExecutorService());
211
212        // Inject element event queue
213        cache.setElementEventQueue(ccm.getElementEventQueue());
214
215        if (auxiliaries != null)
216        {
217            // Next, create the auxiliaries for the new cache
218            final List<AuxiliaryCache<K, V>> auxList = new ArrayList<>();
219
220            log.debug( "Parsing region name \"{0}\", value \"{1}\"", regName, auxiliaries );
221
222            String auxNames[] = auxiliaries.split("\\s*,\\s*");
223
224            // just to be on the safe side...
225            if (auxNames.length == 0)
226            {
227                return null;
228            }
229
230            for (String auxName : auxNames)
231            {
232                if (auxName.isEmpty())
233                {
234                    log.warn( "Skipping empty auxiliary name" );
235                    continue;
236                }
237
238                log.debug( "Parsing auxiliary named \"{0}\".", auxName );
239
240                AuxiliaryCache<K, V> auxCache = parseAuxiliary( props, ccm, auxName, regName );
241
242                if ( auxCache != null )
243                {
244                    if (auxCache instanceof IRequireScheduler)
245                    {
246                        ((IRequireScheduler) auxCache).setScheduledExecutorService(
247                                ccm.getScheduledExecutorService());
248                    }
249
250                    auxList.add( auxCache );
251                }
252            }
253
254            // Associate the auxiliaries with the cache
255            cache.setAuxCaches(auxList);
256        }
257
258        // Return the new cache
259        return cache;
260    }
261
262    protected <K, V> CompositeCache<K, V> newCache(
263            final ICompositeCacheAttributes cca, final IElementAttributes ea)
264    {
265        return new CompositeCache<>( cca, ea );
266    }
267
268    /**
269     * Get an ICompositeCacheAttributes for the listed region.
270     *<p>
271     * @param props Configuration properties
272     * @param regName the region name
273     * @param defaultCCAttr the default cache attributes
274     *
275     * @return ICompositeCacheAttributes
276     */
277    protected ICompositeCacheAttributes parseCompositeCacheAttributes( final Properties props,
278            final String regName, final ICompositeCacheAttributes defaultCCAttr )
279    {
280        return parseCompositeCacheAttributes( props, regName, defaultCCAttr, REGION_PREFIX );
281    }
282
283    /**
284     * Get the main attributes for a region.
285     *<p>
286     * @param props Configuration properties
287     * @param regName the region name
288     * @param defaultCCAttr the default cache attributes
289     * @param regionPrefix the region prefix
290     *
291     * @return ICompositeCacheAttributes
292     */
293    protected ICompositeCacheAttributes parseCompositeCacheAttributes( final Properties props,
294            final String regName, final ICompositeCacheAttributes defaultCCAttr, final String regionPrefix )
295    {
296        ICompositeCacheAttributes ccAttr;
297
298        final String attrName = regionPrefix + regName + CACHE_ATTRIBUTE_PREFIX;
299
300        // auxFactory was not previously initialized.
301        // String prefix = regionPrefix + regName + ATTRIBUTE_PREFIX;
302        ccAttr = OptionConverter.instantiateByKey( props, attrName, null );
303
304        if ( ccAttr == null )
305        {
306            log.info( "No special CompositeCacheAttributes class defined for "
307                    + "key [{0}], using default class.", attrName );
308
309            ccAttr = defaultCCAttr;
310        }
311
312        log.debug( "Parsing options for \"{0}\"", attrName );
313
314        PropertySetter.setProperties( ccAttr, props, attrName + "." );
315        ccAttr.setCacheName( regName );
316
317        log.debug( "End of parsing for \"{0}\"", attrName );
318
319        // GET CACHE FROM FACTORY WITH ATTRIBUTES
320        ccAttr.setCacheName( regName );
321        return ccAttr;
322    }
323
324    /**
325     * Create the element attributes from the properties object for a cache region.
326     *<p>
327     * @param props Configuration properties
328     * @param regName the region name
329     * @param defaultEAttr the default element attributes
330     * @param regionPrefix the region prefix
331     *
332     * @return IElementAttributes
333     */
334    protected IElementAttributes parseElementAttributes( final Properties props, final String regName,
335            final IElementAttributes defaultEAttr, final String regionPrefix )
336    {
337        IElementAttributes eAttr;
338
339        final String attrName = regionPrefix + regName + CompositeCacheConfigurator.ELEMENT_ATTRIBUTE_PREFIX;
340
341        // auxFactory was not previously initialized.
342        // String prefix = regionPrefix + regName + ATTRIBUTE_PREFIX;
343        eAttr = OptionConverter.instantiateByKey( props, attrName, null );
344        if ( eAttr == null )
345        {
346            log.info( "No special ElementAttribute class defined for key [{0}], "
347                    + "using default class.", attrName );
348
349            eAttr = defaultEAttr;
350        }
351
352        log.debug( "Parsing options for \"{0}\"", attrName );
353
354        PropertySetter.setProperties( eAttr, props, attrName + "." );
355        // eAttr.setCacheName( regName );
356
357        log.debug( "End of parsing for \"{0}\"", attrName );
358
359        // GET CACHE FROM FACTORY WITH ATTRIBUTES
360        // eAttr.setCacheName( regName );
361        return eAttr;
362    }
363
364    /**
365     * Get an aux cache for the listed aux for a region.
366     *<p>
367     * @param props the configuration properties
368     * @param ccm Cache hub
369     * @param auxName the name of the auxiliary cache
370     * @param regName the name of the region.
371     * @return AuxiliaryCache
372     */
373    protected <K, V> AuxiliaryCache<K, V> parseAuxiliary( final Properties props, final CompositeCacheManager ccm,
374            final String auxName, final String regName )
375    {
376        log.debug( "parseAuxiliary {0}", auxName );
377
378        // GET CACHE
379        AuxiliaryCache<K, V> auxCache = ccm.getAuxiliaryCache(auxName, regName);
380
381        if (auxCache == null)
382        {
383            // GET FACTORY
384            AuxiliaryCacheFactory auxFac = ccm.registryFacGet( auxName );
385            if ( auxFac == null )
386            {
387                // auxFactory was not previously initialized.
388                final String prefix = AUXILIARY_PREFIX + auxName;
389                auxFac = OptionConverter.instantiateByKey( props, prefix, null );
390                if ( auxFac == null )
391                {
392                    log.error( "Could not instantiate auxFactory named \"{0}\"", auxName );
393                    return null;
394                }
395
396                auxFac.setName( auxName );
397
398                if ( auxFac instanceof IRequireScheduler)
399                {
400                        ((IRequireScheduler)auxFac).setScheduledExecutorService(ccm.getScheduledExecutorService());
401                }
402
403                auxFac.initialize();
404                ccm.registryFacPut( auxFac );
405            }
406
407            // GET ATTRIBUTES
408            AuxiliaryCacheAttributes auxAttr = ccm.registryAttrGet( auxName );
409            final String attrName = AUXILIARY_PREFIX + auxName + ATTRIBUTE_PREFIX;
410            if ( auxAttr == null )
411            {
412                // auxFactory was not previously initialized.
413                final String prefix = AUXILIARY_PREFIX + auxName + ATTRIBUTE_PREFIX;
414                auxAttr = OptionConverter.instantiateByKey( props, prefix, null );
415                if ( auxAttr == null )
416                {
417                    log.error( "Could not instantiate auxAttr named \"{0}\"", attrName );
418                    return null;
419                }
420                auxAttr.setName( auxName );
421                ccm.registryAttrPut( auxAttr );
422            }
423
424            auxAttr = auxAttr.clone();
425
426            log.debug( "Parsing options for \"{0}\"", attrName );
427
428            PropertySetter.setProperties( auxAttr, props, attrName + "." );
429            auxAttr.setCacheName( regName );
430
431            log.debug( "End of parsing for \"{0}\"", attrName );
432
433            // GET CACHE FROM FACTORY WITH ATTRIBUTES
434            auxAttr.setCacheName( regName );
435
436            final String auxPrefix = AUXILIARY_PREFIX + auxName;
437
438            // CONFIGURE THE EVENT LOGGER
439            final ICacheEventLogger cacheEventLogger =
440                    AuxiliaryCacheConfigurator.parseCacheEventLogger( props, auxPrefix );
441
442            // CONFIGURE THE ELEMENT SERIALIZER
443            final IElementSerializer elementSerializer =
444                    AuxiliaryCacheConfigurator.parseElementSerializer( props, auxPrefix );
445
446            // CONFIGURE THE KEYMATCHER
447            //IKeyMatcher keyMatcher = parseKeyMatcher( props, auxPrefix );
448            // TODO add to factory interface
449
450            // Consider putting the compositeCache back in the factory interface
451            // since the manager may not know about it at this point.
452            // need to make sure the manager already has the cache
453            // before the auxiliary is created.
454            try
455            {
456                auxCache = auxFac.createCache( auxAttr, ccm, cacheEventLogger, elementSerializer );
457            }
458            catch (final Exception e)
459            {
460                log.error( "Could not instantiate auxiliary cache named \"{0}\"", regName, e );
461                return null;
462            }
463
464            ccm.addAuxiliaryCache(auxName, regName, auxCache);
465        }
466
467        return auxCache;
468    }
469
470    /**
471     * Any property values will be replaced with system property values that match the key.
472     * <p>
473     * @param props
474     */
475    protected static void overrideWithSystemProperties( final Properties props )
476    {
477        // override any setting with values from the system properties.
478        final Properties sysProps = System.getProperties();
479        for (final String key : sysProps.stringPropertyNames())
480        {
481            if ( key.startsWith( SYSTEM_PROPERTY_KEY_PREFIX ) )
482            {
483                log.info( "Using system property [[{0}] [{1}]]", () -> key,
484                        () -> sysProps.getProperty( key ) );
485                props.setProperty( key, sysProps.getProperty( key ) );
486            }
487        }
488    }
489
490    /**
491     * Creates a custom key matcher if one is defined.  Else, it uses the default.
492     * <p>
493     * @param props
494     * @param auxPrefix - ex. AUXILIARY_PREFIX + auxName
495     * @return IKeyMatcher
496     */
497    protected <K> IKeyMatcher<K> parseKeyMatcher( final Properties props, final String auxPrefix )
498    {
499
500        // auxFactory was not previously initialized.
501        final String keyMatcherClassName = auxPrefix + KEY_MATCHER_PREFIX;
502        IKeyMatcher<K> keyMatcher = OptionConverter.instantiateByKey( props, keyMatcherClassName, null );
503        if ( keyMatcher != null )
504        {
505            final String attributePrefix = auxPrefix + KEY_MATCHER_PREFIX + ATTRIBUTE_PREFIX;
506            PropertySetter.setProperties( keyMatcher, props, attributePrefix + "." );
507            log.info( "Using custom key matcher [{0}] for auxiliary [{1}]", keyMatcher, auxPrefix );
508        }
509        else
510        {
511            // use the default standard serializer
512            keyMatcher = new KeyMatcherPatternImpl<>();
513            log.info( "Using standard key matcher [{0}] for auxiliary [{1}]", keyMatcher, auxPrefix );
514        }
515        return keyMatcher;
516    }
517}