001    package org.apache.jcs.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    
022    import java.io.FileInputStream;
023    import java.io.IOException;
024    import java.io.Serializable;
025    import java.util.ArrayList;
026    import java.util.Enumeration;
027    import java.util.List;
028    import java.util.Properties;
029    import java.util.StringTokenizer;
030    
031    import org.apache.commons.logging.Log;
032    import org.apache.commons.logging.LogFactory;
033    import org.apache.jcs.auxiliary.AuxiliaryCache;
034    import org.apache.jcs.auxiliary.AuxiliaryCacheAttributes;
035    import org.apache.jcs.auxiliary.AuxiliaryCacheConfigurator;
036    import org.apache.jcs.auxiliary.AuxiliaryCacheFactory;
037    import org.apache.jcs.engine.behavior.ICache;
038    import org.apache.jcs.engine.behavior.ICompositeCacheAttributes;
039    import org.apache.jcs.engine.behavior.IElementAttributes;
040    import org.apache.jcs.engine.behavior.IElementSerializer;
041    import org.apache.jcs.engine.behavior.IRequireScheduler;
042    import org.apache.jcs.engine.logging.behavior.ICacheEventLogger;
043    import org.apache.jcs.engine.match.KeyMatcherPatternImpl;
044    import org.apache.jcs.engine.match.behavior.IKeyMatcher;
045    import org.apache.jcs.utils.config.OptionConverter;
046    import org.apache.jcs.utils.config.PropertySetter;
047    
048    /**
049     * This class configures JCS based on a properties object.
050     * <p>
051     * This class is based on the log4j class org.apache.log4j.PropertyConfigurator which was made by:
052     * "Luke Blanshard" <Luke@quiq.com>"Mark DONSZELMANN" <Mark.Donszelmann@cern.ch>"Anders Kristensen"
053     * <akristensen@dynamicsoft.com>
054     */
055    public class CompositeCacheConfigurator
056    {
057        /** The logger */
058        private final static Log log = LogFactory.getLog( CompositeCacheConfigurator.class );
059    
060        /** default region prefix */
061        final static String DEFAULT_REGION = "jcs.default";
062    
063        /** normal region prefix */
064        final static String REGION_PREFIX = "jcs.region.";
065    
066        /** system region prefix. might not be used */
067        final static String SYSTEM_REGION_PREFIX = "jcs.system.";
068    
069        /** auxiliary prefix */
070        final static String AUXILIARY_PREFIX = "jcs.auxiliary.";
071    
072        /** .attributes */
073        final static String ATTRIBUTE_PREFIX = ".attributes";
074    
075        /** .cacheattributes */
076        final static String CACHE_ATTRIBUTE_PREFIX = ".cacheattributes";
077    
078        /** .elementattributes */
079        final static String ELEMENT_ATTRIBUTE_PREFIX = ".elementattributes";
080    
081        /**
082         * jcs.auxiliary.NAME.keymatcher=CLASSNAME
083         * <p>
084         * jcs.auxiliary.NAME.keymatcher.attributes.CUSTOMPROPERTY=VALUE
085         */
086        public final static String KEY_MATCHER_PREFIX = ".keymatcher";
087    
088        /** Can't operate on the interface. */
089        private final CompositeCacheManager compositeCacheManager;
090    
091        /**
092         * Constructor for the CompositeCacheConfigurator object
093         *<p>
094         * @param ccMgr
095         */
096        public CompositeCacheConfigurator( CompositeCacheManager ccMgr )
097        {
098            this.compositeCacheManager = ccMgr;
099        }
100    
101        /**
102         * Configure cached for file name.
103         * <p>
104         * This is only used for testing. The manager handles the translation of a file into a
105         * properties object.
106         * <p>
107         * @param configFileName
108         */
109        protected void doConfigure( String configFileName )
110        {
111            Properties props = new Properties();
112            try
113            {
114                FileInputStream istream = new FileInputStream( configFileName );
115                props.load( istream );
116                istream.close();
117            }
118            catch ( IOException e )
119            {
120                log.error( "Could not read configuration file, ignored: " + configFileName, e );
121                return;
122            }
123    
124            // If we reach here, then the config file is alright.
125            doConfigure( props );
126        }
127    
128        /**
129         * Configure cache for properties object.
130         * <p>
131         * This method proceeds in several steps:
132         * <ul>
133         * <li>Store props for use by non configured caches.
134         * <li>Set default value list
135         * <li>Set default cache attr
136         * <li>Set default element attr
137         * <li>Setup system caches to be used
138         * <li>Setup preconfigured caches
139         * </ul>
140         * @param properties
141         */
142        public void doConfigure( Properties properties )
143        {
144            long start = System.currentTimeMillis();
145    
146            // store props for use by non configured caches
147            compositeCacheManager.setConfigurationProperties( properties );
148    
149            // set default value list
150            setDefaultAuxValues( properties );
151    
152            // set default cache attr
153            setDefaultCompositeCacheAttributes( properties );
154    
155            // set default element attr
156            setDefaultElementAttributes( properties );
157    
158            // set up ssytem caches to be used by non system caches
159            // need to make sure there is no circuarity of reference
160            parseSystemRegions( properties );
161    
162            // setup preconfigured caches
163            parseRegions( properties );
164    
165            long end = System.currentTimeMillis();
166            if ( log.isInfoEnabled() )
167            {
168                log.info( "Finished configuration in " + ( end - start ) + " ms." );
169            }
170    
171        }
172    
173        /**
174         * Set the default aux list for new caches.
175         * <p>
176         * @param props
177         */
178        protected void setDefaultAuxValues( Properties props )
179        {
180            String value = OptionConverter.findAndSubst( DEFAULT_REGION, props );
181            compositeCacheManager.defaultAuxValues = value;
182    
183            if ( log.isInfoEnabled() )
184            {
185                log.info( "Setting default auxiliaries to " + value );
186            }
187        }
188    
189        /**
190         * Set the default CompositeCacheAttributes for new caches.
191         *<p>
192         * @param props
193         */
194        protected void setDefaultCompositeCacheAttributes( Properties props )
195        {
196            ICompositeCacheAttributes icca = parseCompositeCacheAttributes( props, "",
197                                                                            CompositeCacheConfigurator.DEFAULT_REGION );
198            compositeCacheManager.setDefaultCacheAttributes( icca );
199    
200            log.info( "setting defaultCompositeCacheAttributes to " + icca );
201        }
202    
203        /**
204         * Set the default ElementAttributes for new caches.
205         *<p>
206         * @param props
207         */
208        protected void setDefaultElementAttributes( Properties props )
209        {
210            IElementAttributes iea = parseElementAttributes( props, "", CompositeCacheConfigurator.DEFAULT_REGION );
211            compositeCacheManager.setDefaultElementAttributes( iea );
212    
213            log.info( "setting defaultElementAttributes to " + iea );
214        }
215    
216        /**
217         * Create caches used internally. System status gives them creation priority.
218         *<p>
219         * @param props
220         */
221        protected <K extends Serializable, V extends Serializable> void parseSystemRegions( Properties props )
222        {
223            Enumeration<?> en = props.propertyNames();
224            while ( en.hasMoreElements() )
225            {
226                String key = (String) en.nextElement();
227                if ( key.startsWith( SYSTEM_REGION_PREFIX ) && ( key.indexOf( "attributes" ) == -1 ) )
228                {
229                    String regionName = key.substring( SYSTEM_REGION_PREFIX.length() );
230                    String value = OptionConverter.findAndSubst( key, props );
231                    ICache<K, V> cache;
232                    synchronized ( regionName )
233                    {
234                        cache = parseRegion( props, regionName, value, null, SYSTEM_REGION_PREFIX );
235                    }
236                    compositeCacheManager.systemCaches.put( regionName, cache );
237                    // to be available for remote reference they need to be here as
238                    // well
239                    compositeCacheManager.caches.put( regionName, cache );
240                }
241            }
242        }
243    
244        /**
245         * Parse region elements.
246         *<p>
247         * @param props
248         */
249        protected <K extends Serializable, V extends Serializable> void parseRegions( Properties props )
250        {
251            List<String> regionNames = new ArrayList<String>();
252    
253            Enumeration<?> en = props.propertyNames();
254            while ( en.hasMoreElements() )
255            {
256                String key = (String) en.nextElement();
257                if ( key.startsWith( REGION_PREFIX ) && ( key.indexOf( "attributes" ) == -1 ) )
258                {
259                    String regionName = key.substring( REGION_PREFIX.length() );
260    
261                    regionNames.add( regionName );
262    
263                    String auxiliaryList = OptionConverter.findAndSubst( key, props );
264                    ICache<K, V> cache;
265                    synchronized ( regionName )
266                    {
267                        cache = parseRegion( props, regionName, auxiliaryList );
268                    }
269                    compositeCacheManager.caches.put( regionName, cache );
270                }
271            }
272    
273            if ( log.isInfoEnabled() )
274            {
275                log.info( "Parsed regions " + regionNames );
276            }
277        }
278    
279        /**
280         * Create cache region.
281         *<p>
282         * @param props
283         * @param regName
284         * @param value
285         * @return CompositeCache
286         */
287        protected <K extends Serializable, V extends Serializable> CompositeCache<K, V> parseRegion(
288                Properties props, String regName, String value )
289        {
290            return parseRegion( props, regName, value, null, REGION_PREFIX );
291        }
292    
293        /**
294         * Get all the properties for a region and configure its cache.
295         * <p>
296         * This method tells the other parse method the name of the region prefix.
297         *<p>
298         * @param props
299         * @param regName
300         * @param value
301         * @param cca
302         * @return CompositeCache
303         */
304        protected <K extends Serializable, V extends Serializable> CompositeCache<K, V> parseRegion(
305                Properties props, String regName, String value, ICompositeCacheAttributes cca )
306        {
307            return parseRegion( props, regName, value, cca, REGION_PREFIX );
308        }
309    
310        /**
311         * Get all the properties for a region and configure its cache.
312         *<p>
313         * @param props
314         * @param regName
315         * @param value
316         * @param cca
317         * @param regionPrefix
318         * @return CompositeCache
319         */
320        protected <K extends Serializable, V extends Serializable> CompositeCache<K, V> parseRegion(
321                Properties props, String regName, String value,
322                ICompositeCacheAttributes cca, String regionPrefix )
323        {
324            // First, create or get the cache and element attributes, and create
325            // the cache.
326            IElementAttributes ea = parseElementAttributes( props, regName, regionPrefix );
327    
328            CompositeCache<K, V> cache = ( cca == null )
329                ? new CompositeCache<K, V>( regName, parseCompositeCacheAttributes( props, regName, regionPrefix ), ea )
330                : new CompositeCache<K, V>( regName, cca, ea );
331    
332            if (value != null)
333            {
334                // Inject scheduler service
335                if (cache.getMemoryCache() instanceof IRequireScheduler)
336                {
337                    ((IRequireScheduler)cache.getMemoryCache()).setScheduledExecutorService(
338                            compositeCacheManager.getScheduledExecutorService());
339                }
340    
341                // Next, create the auxiliaries for the new cache
342                List<AuxiliaryCache<K, V>> auxList = new ArrayList<AuxiliaryCache<K, V>>();
343    
344                if ( log.isDebugEnabled() )
345                {
346                    log.debug( "Parsing region name '" + regName + "', value '" + value + "'" );
347                }
348    
349                // We must skip over ',' but not white space
350                StringTokenizer st = new StringTokenizer( value, "," );
351    
352                // If value is not in the form ", appender.." or "", then we should set
353                // the priority of the category.
354    
355                if ( !( value.startsWith( "," ) || value.equals( "" ) ) )
356                {
357                    // just to be on the safe side...
358                    if ( !st.hasMoreTokens() )
359                    {
360                        return null;
361                    }
362                }
363    
364                AuxiliaryCache<K, V> auxCache;
365                String auxName;
366                while ( st.hasMoreTokens() )
367                {
368                    auxName = st.nextToken().trim();
369                    if ( auxName == null || auxName.equals( "," ) )
370                    {
371                        continue;
372                    }
373                    log.debug( "Parsing auxiliary named \"" + auxName + "\"." );
374    
375                    auxCache = parseAuxiliary( props, auxName, regName );
376    
377                    if ( auxCache != null )
378                    {
379                        if (auxCache instanceof IRequireScheduler)
380                        {
381                            ((IRequireScheduler)auxCache).setScheduledExecutorService(
382                                    compositeCacheManager.getScheduledExecutorService());
383                        }
384    
385                        auxList.add( auxCache );
386                    }
387                }
388    
389                // Associate the auxiliaries with the cache
390                @SuppressWarnings("unchecked") // No generic arrays in java
391                AuxiliaryCache<K, V>[] auxArray = auxList.toArray( new AuxiliaryCache[0] );
392                cache.setAuxCaches( auxArray );
393            }
394    
395            // Return the new cache
396            return cache;
397        }
398    
399        /**
400         * Get an ICompositeCacheAttributes for the listed region.
401         *<p>
402         * @param props
403         * @param regName
404         * @return ICompositeCacheAttributes
405         */
406        protected ICompositeCacheAttributes parseCompositeCacheAttributes( Properties props, String regName )
407        {
408            return parseCompositeCacheAttributes( props, regName, REGION_PREFIX );
409        }
410    
411        /**
412         * Get the main attributes for a region.
413         *<p>
414         * @param props
415         * @param regName
416         * @param regionPrefix
417         * @return ICompositeCacheAttributes
418         */
419        protected ICompositeCacheAttributes parseCompositeCacheAttributes( Properties props, String regName,
420                                                                           String regionPrefix )
421        {
422            ICompositeCacheAttributes ccAttr;
423    
424            String attrName = regionPrefix + regName + CACHE_ATTRIBUTE_PREFIX;
425    
426            // auxFactory was not previously initialized.
427            // String prefix = regionPrefix + regName + ATTRIBUTE_PREFIX;
428            ccAttr = OptionConverter.instantiateByKey( props, attrName, null );
429    
430            if ( ccAttr == null )
431            {
432                if ( log.isInfoEnabled() )
433                {
434                    log.info( "No special CompositeCacheAttributes class defined for key [" + attrName
435                        + "], using default class." );
436                }
437    
438                ICompositeCacheAttributes ccAttr2 = compositeCacheManager.getDefaultCacheAttributes();
439                ccAttr = ccAttr2.copy();
440            }
441    
442            if ( log.isDebugEnabled() )
443            {
444                log.debug( "Parsing options for '" + attrName + "'" );
445            }
446    
447            PropertySetter.setProperties( ccAttr, props, attrName + "." );
448            ccAttr.setCacheName( regName );
449    
450            if ( log.isDebugEnabled() )
451            {
452                log.debug( "End of parsing for \"" + attrName + "\"." );
453            }
454    
455            // GET CACHE FROM FACTORY WITH ATTRIBUTES
456            ccAttr.setCacheName( regName );
457            return ccAttr;
458        }
459    
460        /**
461         * Create the element attributes from the properties object for a cache region.
462         *<p>
463         * @param props
464         * @param regName
465         * @param regionPrefix
466         * @return IElementAttributes
467         */
468        protected IElementAttributes parseElementAttributes( Properties props, String regName, String regionPrefix )
469        {
470            IElementAttributes eAttr;
471    
472            String attrName = regionPrefix + regName + CompositeCacheConfigurator.ELEMENT_ATTRIBUTE_PREFIX;
473    
474            // auxFactory was not previously initialized.
475            // String prefix = regionPrefix + regName + ATTRIBUTE_PREFIX;
476            eAttr = OptionConverter.instantiateByKey( props, attrName, null );
477            if ( eAttr == null )
478            {
479                if ( log.isInfoEnabled() )
480                {
481                    log.info( "No special ElementAttribute class defined for key [" + attrName + "], using default class." );
482                }
483    
484                IElementAttributes eAttr2 = compositeCacheManager.getDefaultElementAttributes();
485                eAttr = eAttr2.copy();
486            }
487    
488            if ( log.isDebugEnabled() )
489            {
490                log.debug( "Parsing options for '" + attrName + "'" );
491            }
492    
493            PropertySetter.setProperties( eAttr, props, attrName + "." );
494            // eAttr.setCacheName( regName );
495    
496            if ( log.isDebugEnabled() )
497            {
498                log.debug( "End of parsing for \"" + attrName + "\"." );
499            }
500    
501            // GET CACHE FROM FACTORY WITH ATTRIBUTES
502            // eAttr.setCacheName( regName );
503            return eAttr;
504        }
505    
506        /**
507         * Get an aux cache for the listed aux for a region.
508         *<p>
509         * @param props the configuration properties
510         * @param auxName the name of the auxiliary cache
511         * @param regName the name of the region.
512         * @return AuxiliaryCache
513         */
514        protected <K extends Serializable, V extends Serializable> AuxiliaryCache<K, V> parseAuxiliary( Properties props, String auxName, String regName )
515        {
516            AuxiliaryCache<K, V> auxCache;
517    
518            if ( log.isDebugEnabled() )
519            {
520                log.debug( "parseAuxiliary " + auxName );
521            }
522    
523            // GET FACTORY
524            AuxiliaryCacheFactory auxFac = compositeCacheManager.registryFacGet( auxName );
525            if ( auxFac == null )
526            {
527                // auxFactory was not previously initialized.
528                String prefix = AUXILIARY_PREFIX + auxName;
529                auxFac = OptionConverter.instantiateByKey( props, prefix, null );
530                if ( auxFac == null )
531                {
532                    log.error( "Could not instantiate auxFactory named \"" + auxName + "\"." );
533                    return null;
534                }
535    
536                auxFac.setName( auxName );
537    
538                compositeCacheManager.registryFacPut( auxFac );
539            }
540    
541            // GET ATTRIBUTES
542            AuxiliaryCacheAttributes auxAttr = compositeCacheManager.registryAttrGet( auxName );
543            String attrName = AUXILIARY_PREFIX + auxName + ATTRIBUTE_PREFIX;
544            if ( auxAttr == null )
545            {
546                // auxFactory was not previously initialized.
547                String prefix = AUXILIARY_PREFIX + auxName + ATTRIBUTE_PREFIX;
548                auxAttr = OptionConverter.instantiateByKey( props, prefix, null );
549                if ( auxAttr == null )
550                {
551                    log.error( "Could not instantiate auxAttr named '" + attrName + "'" );
552                    return null;
553                }
554                auxAttr.setName( auxName );
555                compositeCacheManager.registryAttrPut( auxAttr );
556            }
557    
558            auxAttr = auxAttr.copy();
559    
560            if ( log.isDebugEnabled() )
561            {
562                log.debug( "Parsing options for '" + attrName + "'" );
563            }
564    
565            PropertySetter.setProperties( auxAttr, props, attrName + "." );
566            auxAttr.setCacheName( regName );
567    
568            if ( log.isDebugEnabled() )
569            {
570                log.debug( "End of parsing for '" + attrName + "'" );
571            }
572    
573            // GET CACHE FROM FACTORY WITH ATTRIBUTES
574            auxAttr.setCacheName( regName );
575    
576            String auxPrefix = AUXILIARY_PREFIX + auxName;
577    
578            // CONFIGURE THE EVENT LOGGER
579            ICacheEventLogger cacheEventLogger = AuxiliaryCacheConfigurator.parseCacheEventLogger( props, auxPrefix );
580    
581            // CONFIGURE THE ELEMENT SERIALIZER
582            IElementSerializer elementSerializer = AuxiliaryCacheConfigurator.parseElementSerializer( props, auxPrefix );
583    
584            // CONFIGURE THE KEYMATCHER
585            //IKeyMatcher keyMatcher = parseKeyMatcher( props, auxPrefix );
586            // TODO add to factory interface
587    
588            // Consider putting the compositeCache back in the factory interface
589            // since the manager may not know about it at this point.
590            // need to make sure the manager already has the cache
591            // before the auxiliary is created.
592            auxCache = auxFac.createCache( auxAttr, compositeCacheManager, cacheEventLogger, elementSerializer );
593    
594            return auxCache;
595        }
596    
597        /**
598         * Creates a custom key matcher if one is defined.  Else, it uses the default.
599         * <p>
600         * @param props
601         * @param auxPrefix - ex. AUXILIARY_PREFIX + auxName
602         * @return IKeyMatcher
603         */
604        public static <K extends Serializable> IKeyMatcher<K> parseKeyMatcher( Properties props, String auxPrefix )
605        {
606    
607            // auxFactory was not previously initialized.
608            String keyMatcherClassName = auxPrefix + KEY_MATCHER_PREFIX;
609            IKeyMatcher<K> keyMatcher = OptionConverter.instantiateByKey( props, keyMatcherClassName, null );
610            if ( keyMatcher != null )
611            {
612                String attributePrefix = auxPrefix + KEY_MATCHER_PREFIX + ATTRIBUTE_PREFIX;
613                PropertySetter.setProperties( keyMatcher, props, attributePrefix + "." );
614                if ( log.isInfoEnabled() )
615                {
616                    log.info( "Using custom key matcher [" + keyMatcher + "] for auxiliary [" + auxPrefix
617                        + "]" );
618                }
619            }
620            else
621            {
622                // use the default standard serializer
623                keyMatcher = new KeyMatcherPatternImpl<K>();
624                if ( log.isInfoEnabled() )
625                {
626                    log.info( "Using standard key matcher [" + keyMatcher + "] for auxiliary [" + auxPrefix + "]" );
627                }
628            }
629            return keyMatcher;
630        }
631    }