View Javadoc
1   package org.apache.commons.jcs.access;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.Map;
26  import java.util.Set;
27  
28  import org.apache.commons.jcs.JCS;
29  import org.apache.commons.jcs.access.behavior.ICacheAccess;
30  import org.apache.commons.jcs.access.exception.CacheException;
31  import org.apache.commons.jcs.access.exception.ConfigurationException;
32  import org.apache.commons.jcs.engine.behavior.ICacheElement;
33  import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
34  import org.apache.commons.jcs.engine.behavior.IElementAttributes;
35  import org.apache.commons.jcs.engine.stats.behavior.ICacheStats;
36  import org.apache.commons.jcs.utils.props.AbstractPropertyContainer;
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  
40  /**
41   * TODO:  Add new methods that will allow you to provide a partition indicator for all major calls.  Add an interface as well.
42   * <p>
43   * This handles dividing puts and gets.
44   * <p>
45   * There are two required properties.
46   * <p>
47   * <ol>
48   * <li>.numberOfPartitions</li>
49   * <li>.partitionRegionNamePrefix</li>
50   * </ol>
51   * System properties will override values in the properties file.
52   * <p>
53   * We use a JCS region name for each partition that looks like this: partitionRegionNamePrefix + "_"
54   * + partitionNumber. The number is 0 indexed based.
55   * <p>
56   * @author Aaron Smuts
57   */
58  public class PartitionedCacheAccess<K, V>
59      extends AbstractPropertyContainer
60      implements ICacheAccess<K, V>
61  {
62      /** the logger. */
63      private static final Log log = LogFactory.getLog( PartitionedCacheAccess.class );
64  
65      /** The number of partitions. */
66      private int numberOfPartitions = 1;
67  
68      /**
69       * We use a JCS region name for each partition that looks like this: partitionRegionNamePrefix +
70       * "_" + partitionNumber
71       */
72      private String partitionRegionNamePrefix;
73  
74      /** An array of partitions built during initialization. */
75      private ICacheAccess<K, V>[] partitions;
76  
77      /** Is the class initialized. */
78      private boolean initialized = false;
79  
80      /** Sets default properties heading and group. */
81      public PartitionedCacheAccess()
82      {
83          setPropertiesHeading( "PartitionedCacheAccess" );
84          setPropertiesGroup( "cache" );
85      }
86  
87      /**
88       * Puts the value into the appropriate cache partition.
89       * <p>
90       * @param key key
91       * @param object object
92       * @throws CacheException on configuration problem
93       */
94      @Override
95      public void put( K key, V object )
96          throws CacheException
97      {
98          if ( key == null || object == null )
99          {
100             log.warn( "Bad input key [" + key + "].  Cannot put null into the cache." );
101             return;
102         }
103 
104         if (!ensureInit())
105         {
106             return;
107         }
108 
109         int partition = getPartitionNumberForKey( key );
110         try
111         {
112             partitions[partition].put( key, object );
113         }
114         catch ( CacheException e )
115         {
116             log.error( "Problem putting value for key [" + key + "] in cache [" + partitions[partition] + "]" );
117             throw e;
118         }
119     }
120 
121     /**
122      * Puts in cache if an item does not exist with the name in that region.
123      * <p>
124      * @param key
125      * @param object
126      * @throws CacheException
127      */
128     @Override
129     public void putSafe( K key, V object )
130         throws CacheException
131     {
132         if ( key == null || object == null )
133         {
134             log.warn( "Bad input key [" + key + "].  Cannot putSafe null into the cache." );
135         }
136 
137         if (!ensureInit())
138         {
139             return;
140         }
141 
142         int partition = getPartitionNumberForKey( key );
143         partitions[partition].putSafe( key, object );
144     }
145 
146     /**
147      * Puts the value into the appropriate cache partition.
148      * <p>
149      * @param key key
150      * @param object object
151      * @param attr
152      * @throws CacheException on configuration problem
153      */
154     @Override
155     public void put( K key, V object, IElementAttributes attr )
156         throws CacheException
157     {
158         if ( key == null || object == null )
159         {
160             log.warn( "Bad input key [" + key + "].  Cannot put null into the cache." );
161             return;
162         }
163 
164         if (!ensureInit())
165         {
166             return;
167         }
168 
169         int partition = getPartitionNumberForKey( key );
170 
171         try
172         {
173             partitions[partition].put( key, object, attr );
174         }
175         catch ( CacheException e )
176         {
177             log.error( "Problem putting value for key [" + key + "] in cache [" + partitions[partition] + "]" );
178             throw e;
179         }
180     }
181 
182     /**
183      * Gets the object for the key from the desired partition.
184      * <p>
185      * @param key key
186      * @return result, null if not found.
187      */
188     @Override
189     public V get( K key )
190     {
191         if ( key == null )
192         {
193             log.warn( "Input key is null." );
194             return null;
195         }
196 
197         if (!ensureInit())
198         {
199             return null;
200         }
201 
202         int partition = getPartitionNumberForKey( key );
203 
204         return partitions[partition].get( key );
205     }
206 
207     /**
208      * Gets the ICacheElement&lt;K, V&gt; (the wrapped object) for the key from the desired partition.
209      * <p>
210      * @param key key
211      * @return result, null if not found.
212      */
213     @Override
214     public ICacheElement<K, V> getCacheElement( K key )
215     {
216         if ( key == null )
217         {
218             log.warn( "Input key is null." );
219             return null;
220         }
221 
222         if (!ensureInit())
223         {
224             return null;
225         }
226 
227         int partition = getPartitionNumberForKey( key );
228 
229         return partitions[partition].getCacheElement( key );
230     }
231 
232     /**
233      * This is a getMultiple. We try to group the keys so that we make as few calls as needed.
234      * <p>
235      * @param names
236      * @return Map of keys to ICacheElement
237      */
238     @Override
239     public Map<K, ICacheElement<K, V>> getCacheElements( Set<K> names )
240     {
241         if ( names == null )
242         {
243             log.warn( "Bad input names cannot be null." );
244             return Collections.emptyMap();
245         }
246 
247         if (!ensureInit())
248         {
249             return Collections.emptyMap();
250         }
251 
252         @SuppressWarnings("unchecked") // No generic arrays in java
253         Set<K>[] dividedNames = new Set[this.getNumberOfPartitions()];
254 
255         for (K key : names)
256         {
257             int partition = getPartitionNumberForKey( key );
258             if ( dividedNames[partition] == null )
259             {
260                 dividedNames[partition] = new HashSet<K>();
261             }
262             dividedNames[partition].add( key );
263         }
264 
265         Map<K, ICacheElement<K, V>> result = new HashMap<K, ICacheElement<K, V>>();
266         for ( int i = 0; i < partitions.length; i++ )
267         {
268             if ( dividedNames[i] != null && !dividedNames[i].isEmpty() )
269             {
270                 result.putAll( partitions[i].getCacheElements( dividedNames[i] ) );
271             }
272         }
273         return result;
274     }
275 
276     /**
277      * This is tricky. Do we need to get from all the partitions?
278      * <p>
279      * If this interface took an object, we could use the hashcode to determine the partition. Then
280      * we could use the toString for the pattern.
281      * <p>
282      * @param pattern
283      * @return HashMap key to value
284      */
285     @Override
286     public Map<K, V> getMatching( String pattern )
287     {
288         if ( pattern == null )
289         {
290             log.warn( "Input pattern is null." );
291             return null;
292         }
293 
294         if (!ensureInit())
295         {
296             return null;
297         }
298 
299         Map<K, V> result = new HashMap<K, V>();
300         for (ICacheAccess<K, V> partition : partitions)
301         {
302             result.putAll( partition.getMatching( pattern ) );
303         }
304 
305         return result;
306     }
307 
308     /**
309      * This is tricky. Do we need to get from all the partitions?
310      * <p>
311      * @param pattern
312      * @return HashMap key to ICacheElement
313      */
314     @Override
315     public Map<K, ICacheElement<K, V>> getMatchingCacheElements( String pattern )
316     {
317         if ( pattern == null )
318         {
319             log.warn( "Input pattern is null." );
320             return null;
321         }
322 
323         if (!ensureInit())
324         {
325             return null;
326         }
327 
328         Map<K, ICacheElement<K, V>> result = new HashMap<K, ICacheElement<K, V>>();
329         for (ICacheAccess<K, V> partition : partitions)
330         {
331             result.putAll( partition.getMatchingCacheElements( pattern ) );
332         }
333         return result;
334     }
335 
336     /**
337      * Removes the item from the appropriate partition.
338      * <p>
339      * @param key
340      * @throws CacheException
341      */
342     @Override
343     public void remove( K key )
344         throws CacheException
345     {
346         if ( key == null )
347         {
348             log.warn( "Input key is null. Cannot remove null from the cache." );
349             return;
350         }
351 
352         if (!ensureInit())
353         {
354             return;
355         }
356 
357         int partition = getPartitionNumberForKey( key );
358         try
359         {
360             partitions[partition].remove( key );
361         }
362         catch ( CacheException e )
363         {
364             log.error( "Problem removing value for key [" + key + "] in cache [" + partitions[partition] + "]" );
365             throw e;
366         }
367     }
368 
369     /**
370      * Calls free on each partition.
371      * <p>
372      * @param numberToFree
373      * @return number removed
374      * @throws CacheException
375      */
376     @Override
377     public int freeMemoryElements( int numberToFree )
378         throws CacheException
379     {
380         if (!ensureInit())
381         {
382             return 0;
383         }
384 
385         int count = 0;
386         for (ICacheAccess<K, V> partition : partitions)
387         {
388             count += partition.freeMemoryElements( numberToFree );
389         }
390         return count;
391     }
392 
393     /**
394      * @return ICompositeCacheAttributes from the first partition.
395      */
396     @Override
397     public ICompositeCacheAttributes getCacheAttributes()
398     {
399         if (!ensureInit())
400         {
401             return null;
402         }
403 
404         if ( partitions.length == 0 )
405         {
406             return null;
407         }
408 
409         return partitions[0].getCacheAttributes();
410     }
411 
412     /**
413      * @return IElementAttributes from the first partition.
414      * @throws CacheException
415      */
416     @Override
417     public IElementAttributes getDefaultElementAttributes()
418         throws CacheException
419     {
420         if (!ensureInit())
421         {
422             return null;
423         }
424 
425         if ( partitions.length == 0 )
426         {
427             return null;
428         }
429 
430         return partitions[0].getDefaultElementAttributes();
431     }
432 
433     /**
434      * This is no more efficient than simply getting the cache element.
435      * <p>
436      * @param key
437      * @return IElementAttributes
438      * @throws CacheException
439      */
440     @Override
441     public IElementAttributes getElementAttributes( K key )
442         throws CacheException
443     {
444         if ( key == null )
445         {
446             log.warn( "Input key is null. Cannot getElementAttributes for null from the cache." );
447             return null;
448         }
449 
450         if (!ensureInit())
451         {
452             return null;
453         }
454 
455         int partition = getPartitionNumberForKey( key );
456 
457         return partitions[partition].getElementAttributes( key );
458     }
459 
460     /**
461      * Resets the attributes for this item. This has the same effect as an update, in most cases.
462      * None of the auxiliaries are optimized to do this more efficiently than a simply update.
463      * <p>
464      * @param key
465      * @param attributes
466      * @throws CacheException
467      */
468     @Override
469     public void resetElementAttributes( K key, IElementAttributes attributes )
470         throws CacheException
471     {
472         if ( key == null )
473         {
474             log.warn( "Input key is null. Cannot resetElementAttributes for null." );
475             return;
476         }
477 
478         if (!ensureInit())
479         {
480             return;
481         }
482 
483         int partition = getPartitionNumberForKey( key );
484 
485         partitions[partition].resetElementAttributes( key, attributes );
486     }
487 
488     /**
489      * Sets the attributes on all the partitions.
490      * <p>
491      * @param cattr
492      */
493     @Override
494     public void setCacheAttributes( ICompositeCacheAttributes cattr )
495     {
496         if (!ensureInit())
497         {
498             return;
499         }
500 
501         for (ICacheAccess<K, V> partition : partitions)
502         {
503             partition.setCacheAttributes( cattr );
504         }
505     }
506 
507     /**
508      * Removes all of the elements from a region.
509      * <p>
510      * @throws CacheException
511      */
512     @Override
513     public void clear()
514         throws CacheException
515     {
516         if (!ensureInit())
517         {
518             return;
519         }
520 
521         for (ICacheAccess<K, V> partition : partitions)
522         {
523             partition.clear();
524         }
525     }
526 
527     /**
528      * This method is does not reset the attributes for items already in the cache. It could
529      * potentially do this for items in memory, and maybe on disk (which would be slow) but not
530      * remote items. Rather than have unpredictable behavior, this method just sets the default
531      * attributes. Items subsequently put into the cache will use these defaults if they do not
532      * specify specific attributes.
533      * <p>
534      * @param attr the default attributes.
535      * @throws CacheException if something goes wrong.
536      */
537     @Override
538     public void setDefaultElementAttributes( IElementAttributes attr )
539         throws CacheException
540     {
541         if (!ensureInit())
542         {
543             return;
544         }
545 
546         for (ICacheAccess<K, V> partition : partitions)
547         {
548             partition.setDefaultElementAttributes(attr);
549         }
550     }
551 
552     /**
553      * This returns the ICacheStats object with information on this region and its auxiliaries.
554      * <p>
555      * This data can be formatted as needed.
556      * <p>
557      * @return ICacheStats
558      */
559     @Override
560     public ICacheStats getStatistics()
561     {
562         if (!ensureInit())
563         {
564             return null;
565         }
566 
567         if ( partitions.length == 0 )
568         {
569             return null;
570         }
571 
572         return partitions[0].getStatistics();
573     }
574 
575     /**
576      * @return A String version of the stats.
577      */
578     @Override
579     public String getStats()
580     {
581         if (!ensureInit())
582         {
583             return "";
584         }
585 
586         StringBuilder stats = new StringBuilder();
587         for (ICacheAccess<K, V> partition : partitions)
588         {
589             stats.append(partition.getStats());
590             stats.append("\n");
591         }
592 
593         return stats.toString();
594     }
595 
596     /**
597      * Dispose this region. Flushes objects to and closes auxiliary caches. This is a shutdown
598      * command!
599      * <p>
600      * To simply remove all elements from the region use clear().
601      */
602     @Override
603     public synchronized void dispose()
604     {
605         if (!ensureInit())
606         {
607             return;
608         }
609 
610         for (ICacheAccess<K, V> partition : partitions)
611         {
612             partition.dispose();
613         }
614 
615         initialized = false;
616     }
617 
618     /**
619      * This expects a numeric key. If the key cannot be converted into a number, we will return 0.
620      * TODO we could md5 it or get the hashcode.
621      * <p>
622      * We determine the partition by taking the mod of the number of partitions.
623      * <p>
624      * @param key key
625      * @return the partition number.
626      */
627     protected int getPartitionNumberForKey( K key )
628     {
629         if ( key == null )
630         {
631             return 0;
632         }
633 
634         long keyNum = getNumericValueForKey( key );
635 
636         int partition = (int) ( keyNum % getNumberOfPartitions() );
637 
638         if ( log.isDebugEnabled() )
639         {
640             log.debug( "Using partition [" + partition + "] for key [" + key + "]" );
641         }
642 
643         return partition;
644     }
645 
646     /**
647      * This can be overridden for special purposes.
648      * <p>
649      * @param key key
650      * @return long
651      */
652     public long getNumericValueForKey( K key )
653     {
654         String keyString = key.toString();
655         long keyNum = -1;
656         try
657         {
658             keyNum = Long.parseLong( keyString );
659         }
660         catch ( NumberFormatException e )
661         {
662             // THIS IS UGLY, but I can't think of a better failsafe right now.
663             keyNum = key.hashCode();
664             log.warn( "Couldn't convert [" + key + "] into a number.  Will use hashcode [" + keyNum + "]" );
665         }
666         return keyNum;
667     }
668 
669     /**
670      * Initialize if we haven't already.
671      * <p>
672      * @throws ConfigurationException on configuration problem
673      */
674     protected synchronized boolean ensureInit()
675     {
676         if ( !initialized )
677         {
678             try
679             {
680                 initialize();
681             }
682             catch ( ConfigurationException e )
683             {
684                 log.error( "Couldn't configure partioned access.", e );
685                 return false;
686             }
687         }
688 
689         return true;
690     }
691 
692     /**
693      * Use the partition prefix and the number of partitions to get JCS regions.
694      * <p>
695      * @throws ConfigurationException on configuration problem
696      */
697     protected synchronized void initialize()
698         throws ConfigurationException
699     {
700         ensureProperties();
701 
702         @SuppressWarnings("unchecked") // No generic arrays in java
703         ICacheAccess<K, V>[] tempPartitions = new ICacheAccess[this.getNumberOfPartitions()];
704         for ( int i = 0; i < this.getNumberOfPartitions(); i++ )
705         {
706             String regionName = this.getPartitionRegionNamePrefix() + "_" + i;
707             try
708             {
709                 tempPartitions[i] = JCS.getInstance( regionName );
710             }
711             catch ( CacheException e )
712             {
713                 log.error( "Problem getting cache for region [" + regionName + "]" );
714             }
715         }
716 
717         partitions = tempPartitions;
718         initialized = true;
719     }
720 
721     /**
722      * Loads in the needed configuration settings. System properties are checked first. A system
723      * property will override local property value.
724      * <p>
725      * Loads the following JCS Cache specific properties:
726      * <ul>
727      * <li>heading.numberOfPartitions</li>
728      * <li>heading.partitionRegionNamePrefix</li>
729      * </ul>
730      * @throws ConfigurationException on configuration problem
731      */
732     @Override
733     protected void handleProperties()
734         throws ConfigurationException
735     {
736         // Number of Partitions.
737         String numberOfPartitionsPropertyName = this.getPropertiesHeading() + ".numberOfPartitions";
738         String numberOfPartitionsPropertyValue = getPropertyForName( numberOfPartitionsPropertyName, true );
739         try
740         {
741             this.setNumberOfPartitions( Integer.parseInt( numberOfPartitionsPropertyValue ) );
742         }
743         catch ( NumberFormatException e )
744         {
745             String message = "Could not convert [" + numberOfPartitionsPropertyValue + "] into a number for ["
746                 + numberOfPartitionsPropertyName + "]";
747             log.error( message );
748             throw new ConfigurationException( message );
749         }
750 
751         // Partition Name Prefix.
752         String prefixPropertyName = this.getPropertiesHeading() + ".partitionRegionNamePrefix";
753         String prefix = getPropertyForName( prefixPropertyName, true );
754         this.setPartitionRegionNamePrefix( prefix );
755     }
756 
757     /**
758      * Checks the system properties before the properties.
759      * <p>
760      * @param propertyName name
761      * @param required is it required?
762      * @return the property value if one is found
763      * @throws ConfigurationException thrown if it is required and not found.
764      */
765     protected String getPropertyForName( String propertyName, boolean required )
766         throws ConfigurationException
767     {
768         String propertyValue = null;
769         propertyValue = System.getProperty( propertyName );
770         if ( propertyValue != null )
771         {
772             if ( log.isInfoEnabled() )
773             {
774                 log.info( "Found system property override: Name [" + propertyName + "] Value [" + propertyValue + "]" );
775             }
776         }
777         else
778         {
779             propertyValue = this.getProperties().getProperty( propertyName );
780             if ( required && propertyValue == null )
781             {
782                 String message = "Could not find required property [" + propertyName + "] in propertiesGroup ["
783                     + this.getPropertiesGroup() + "]";
784                 log.error( message );
785                 throw new ConfigurationException( message );
786             }
787             else
788             {
789                 if ( log.isInfoEnabled() )
790                 {
791                     log.info( "Name [" + propertyName + "] Value [" + propertyValue + "]" );
792                 }
793             }
794         }
795         return propertyValue;
796     }
797 
798     /**
799      * @param numberOfPartitions The numberOfPartitions to set.
800      */
801     protected void setNumberOfPartitions( int numberOfPartitions )
802     {
803         this.numberOfPartitions = numberOfPartitions;
804     }
805 
806     /**
807      * @return Returns the numberOfPartitions.
808      */
809     protected int getNumberOfPartitions()
810     {
811         return numberOfPartitions;
812     }
813 
814     /**
815      * @param partitionRegionNamePrefix The partitionRegionNamePrefix to set.
816      */
817     protected void setPartitionRegionNamePrefix( String partitionRegionNamePrefix )
818     {
819         this.partitionRegionNamePrefix = partitionRegionNamePrefix;
820     }
821 
822     /**
823      * @return Returns the partitionRegionNamePrefix.
824      */
825     protected String getPartitionRegionNamePrefix()
826     {
827         return partitionRegionNamePrefix;
828     }
829 
830     /**
831      * @param partitions The partitions to set.
832      */
833     protected void setPartitions( ICacheAccess<K, V>[] partitions )
834     {
835         this.partitions = partitions;
836     }
837 
838     /**
839      * @return Returns the partitions.
840      */
841     protected ICacheAccess<K, V>[] getPartitions()
842     {
843         return partitions;
844     }
845 }