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 }