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 }