View Javadoc

1   /*
2    * Copyright 2008 University Corporation for Advanced Internet Development, Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.opensaml.xml.util;
18  
19  import java.util.Map;
20  import java.util.concurrent.locks.Lock;
21  import java.util.concurrent.locks.ReadWriteLock;
22  import java.util.concurrent.locks.ReentrantReadWriteLock;
23  
24  import net.jcip.annotations.ThreadSafe;
25  
26  /**
27   * This class is used to store instances of objects that may be created independently but are, in face, the same object.
28   * For example, {@link org.opensaml.xml.signature.KeyInfo}s contain keys, certs, and CRLs. Multiple unique instances of
29   * a KeyInfo may contain, and separately construct, the exact same cert. KeyInfo could, therefore, create a class-level
30   * instance of this object store and put certs within it. In this manner the cert is only sitting in memory once and
31   * each KeyInfo simply stores a reference (index) to stored object.
32   * 
33   * This store uses basic reference counting to keep track of how many of the respective objects are pointing to an
34   * entry. Adding an object that already exists, as determined by the objects <code>hashCode()</code> method, simply
35   * increments the reference counter. Removing an object decrements the counter. Only when the counter reaches zero is
36   * the object actually freed for garbage collection.
37   * 
38   * <strong>Note</strong> the instance of an object returned by {@link #get(String)} need not be the same object as 
39   * stored via {@link #put(Object)}.  However, their hash codes will be equal.  Therefore this store should never be 
40   * used to store objects that produce identical hash codes but are not functionally identical objects.
41   * 
42   * @param <T> type of object being stored
43   */
44  @ThreadSafe
45  public class IndexingObjectStore<T> {
46  
47      /** Read/Write lock used to control synchronization over the backing data store. */
48      private ReadWriteLock rwLock;
49  
50      /** Backing object data store. */
51      private Map<String, StoredObjectWrapper> objectStore;
52  
53      /** Constructor. */
54      public IndexingObjectStore() {
55          rwLock = new ReentrantReadWriteLock();
56          objectStore = new LazyMap<String, StoredObjectWrapper>();
57      }
58  
59      /** Clears the object store. */
60      public void clear() {
61          Lock writeLock = rwLock.writeLock();
62          writeLock.lock();
63          try {
64              objectStore.clear();
65          } finally {
66              writeLock.unlock();
67          }
68  
69      }
70  
71      /**
72       * Checks whether the store contains an object registered under the given index.
73       * 
74       * @param index the index to check
75       * 
76       * @return true if an object is associated with the given index, false if not
77       */
78      public boolean contains(String index) {
79          Lock readLock = rwLock.readLock();
80          readLock.lock();
81          try {
82              return objectStore.containsKey(index);
83          } finally {
84              readLock.unlock();
85          }
86      }
87  
88      /**
89       * Checks if the store is empty.
90       * 
91       * @return true if the store is empty, false if not
92       */
93      public boolean isEmpty() {
94          return objectStore.isEmpty();
95      }
96  
97      /**
98       * Adds the given object to the store. Technically this method only adds the object if it does not already exist in
99       * the store. If it does this method simply increments the reference count of the object.
100      * 
101      * @param object the object to add to the store, may be null
102      * 
103      * @return the index that may be used to later retrieve the object or null if the object was null
104      */
105     public String put(T object) {
106         if (object == null) {
107             return null;
108         }
109 
110         Lock writeLock = rwLock.writeLock();
111         writeLock.lock();
112         try {
113             String index = Integer.toString(object.hashCode());
114 
115             StoredObjectWrapper objectWrapper = objectStore.get(index);
116             if (objectWrapper == null) {
117                 objectWrapper = new StoredObjectWrapper(object);
118                 objectStore.put(index, objectWrapper);
119             }
120             objectWrapper.incremementReferenceCount();
121 
122             return index;
123         } finally {
124             writeLock.unlock();
125         }
126     }
127 
128     /**
129      * Gets a registered object by its index.
130      * 
131      * @param index the index of an object previously registered, may be null
132      * 
133      * @return the registered object or null if no object is registered for that index
134      */
135     public T get(String index) {
136         if (index == null) {
137             return null;
138         }
139 
140         Lock readLock = rwLock.readLock();
141         readLock.lock();
142         try {
143             StoredObjectWrapper objectWrapper = objectStore.get(index);
144             if (objectWrapper != null) {
145                 return objectWrapper.getObject();
146             }
147 
148             return null;
149         } finally {
150             readLock.unlock();
151         }
152     }
153 
154     /**
155      * Removes the object associated with the given index. Technically this method decrements the reference counter to
156      * the object. If, after the decrement, the reference counter is zero then, and only then, is the object actually
157      * freed for garbage collection.
158      * 
159      * @param index the index of the object, may be null
160      */
161     public void remove(String index) {
162         if (index == null) {
163             return;
164         }
165 
166         Lock writeLock = rwLock.writeLock();
167         writeLock.lock();
168         try {
169             StoredObjectWrapper objectWrapper = objectStore.get(index);
170             if (objectWrapper != null) {
171                 objectWrapper.decremementReferenceCount();
172                 if (objectWrapper.getReferenceCount() == 0) {
173                     objectStore.remove(index);
174                 }
175             }
176         } finally {
177             writeLock.unlock();
178         }
179     }
180 
181     /**
182      * Gets the total number of unique items in the store. This number is unaffected by the reference count of the
183      * individual stored objects.
184      * 
185      * @return number of items in the store
186      */
187     public int size() {
188         return objectStore.size();
189     }
190 
191     /** Wrapper class that keeps track of the reference count for a stored object. */
192     private class StoredObjectWrapper {
193 
194         /** The stored object. */
195         private T object;
196 
197         /** The object reference count. */
198         private int referenceCount;
199 
200         /**
201          * Constructor.
202          * 
203          * @param wrappedObject the object being wrapped
204          */
205         public StoredObjectWrapper(T wrappedObject) {
206             object = wrappedObject;
207             referenceCount = 0;
208         }
209 
210         /**
211          * Gets the wrapped object.
212          * 
213          * @return the wrapped object
214          */
215         public T getObject() {
216             return object;
217         }
218 
219         /**
220          * Gets the current reference count.
221          * 
222          * @return current reference count
223          */
224         public int getReferenceCount() {
225             return referenceCount;
226         }
227 
228         /** Increments the current reference count by one. */
229         public void incremementReferenceCount() {
230             referenceCount += 1;
231         }
232 
233         /** Decrements the current reference count by one. */
234         public void decremementReferenceCount() {
235             referenceCount -= 1;
236         }
237     }
238 }