This project has retired. For details please refer to its Attic page.
CacheImpl xref

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.chemistry.opencmis.client.runtime.cache;
20  
21  import java.io.IOException;
22  import java.io.ObjectInputStream;
23  import java.io.ObjectOutputStream;
24  import java.io.Serializable;
25  import java.lang.ref.SoftReference;
26  import java.util.HashMap;
27  import java.util.LinkedHashMap;
28  import java.util.Map;
29  import java.util.concurrent.locks.ReentrantReadWriteLock;
30  
31  import org.apache.chemistry.opencmis.client.api.CmisObject;
32  import org.apache.chemistry.opencmis.client.api.Session;
33  import org.apache.chemistry.opencmis.commons.PropertyIds;
34  import org.apache.chemistry.opencmis.commons.SessionParameter;
35  
36  /**
37   * Synchronized cache implementation. The cache is limited to a specific size of
38   * entries and works in a LRU mode.
39   */
40  public class CacheImpl implements Cache {
41  
42      private static final long serialVersionUID = 1L;
43  
44      private static final float HASHTABLE_LOAD_FACTOR = 0.75f;
45  
46      private int cacheSize;
47      private int cacheTtl;
48      private int pathToIdSize;
49      private int pathToIdTtl;
50  
51      private LinkedHashMap<String, CacheItem<Map<String, CmisObject>>> objectMap;
52      private LinkedHashMap<String, CacheItem<String>> pathToIdMap;
53  
54      private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
55  
56      /**
57       * Default constructor.
58       */
59      public CacheImpl() {
60      }
61  
62      public void initialize(Session session, Map<String, String> parameters) {
63          lock.writeLock().lock();
64          try {
65              // cache size
66              try {
67                  cacheSize = Integer.valueOf(parameters.get(SessionParameter.CACHE_SIZE_OBJECTS));
68                  if (cacheSize < 0) {
69                      cacheSize = 0;
70                  }
71              } catch (Exception e) {
72                  cacheSize = 1000;
73              }
74  
75              // cache time-to-live
76              try {
77                  cacheTtl = Integer.valueOf(parameters.get(SessionParameter.CACHE_TTL_OBJECTS));
78                  if (cacheTtl < 0) {
79                      cacheTtl = 2 * 60 * 60 * 1000;
80                  }
81              } catch (Exception e) {
82                  cacheTtl = 2 * 60 * 60 * 1000;
83              }
84  
85              // path-to-id size
86              try {
87                  pathToIdSize = Integer.valueOf(parameters.get(SessionParameter.CACHE_SIZE_PATHTOID));
88                  if (pathToIdSize < 0) {
89                      pathToIdSize = 0;
90                  }
91              } catch (Exception e) {
92                  pathToIdSize = 1000;
93              }
94  
95              // path-to-id time-to-live
96              try {
97                  pathToIdTtl = Integer.valueOf(parameters.get(SessionParameter.CACHE_TTL_PATHTOID));
98                  if (pathToIdTtl < 0) {
99                      pathToIdTtl = 30 * 60 * 1000;
100                 }
101             } catch (Exception e) {
102                 pathToIdTtl = 30 * 60 * 1000;
103             }
104 
105             initializeInternals();
106         } finally {
107             lock.writeLock().unlock();
108         }
109     }
110 
111     /**
112      * Sets up the internal objects.
113      */
114     private void initializeInternals() {
115         lock.writeLock().lock();
116         try {
117             // object cache
118             int cacheHashTableCapacity = (int) Math.ceil(cacheSize / HASHTABLE_LOAD_FACTOR) + 1;
119 
120             final int cs = cacheSize;
121 
122             objectMap = new LinkedHashMap<String, CacheItem<Map<String, CmisObject>>>(cacheHashTableCapacity,
123                     HASHTABLE_LOAD_FACTOR) {
124 
125                 private static final long serialVersionUID = 1L;
126 
127                 @Override
128                 protected boolean removeEldestEntry(Map.Entry<String, CacheItem<Map<String, CmisObject>>> eldest) {
129                     return size() > cs;
130                 }
131             };
132 
133             // path-to-id mapping
134             int pathtoidHashTableCapacity = (int) Math.ceil(pathToIdSize / HASHTABLE_LOAD_FACTOR) + 1;
135 
136             final int ptis = pathToIdSize;
137 
138             pathToIdMap = new LinkedHashMap<String, CacheItem<String>>(pathtoidHashTableCapacity, HASHTABLE_LOAD_FACTOR) {
139 
140                 private static final long serialVersionUID = 1L;
141 
142                 @Override
143                 protected boolean removeEldestEntry(Map.Entry<String, CacheItem<String>> eldest) {
144                     return size() > ptis;
145                 }
146             };
147         } finally {
148             lock.writeLock().unlock();
149         }
150     }
151 
152     public void clear() {
153         initializeInternals();
154     }
155 
156     public boolean containsId(String objectId, String cacheKey) {
157         lock.writeLock().lock();
158         try {
159             if (!objectMap.containsKey(objectId)) {
160                 return false;
161             }
162 
163             CacheItem<Map<String, CmisObject>> item = objectMap.get(objectId);
164             if (item.isExpired()) {
165                 objectMap.remove(objectId);
166                 return false;
167             }
168 
169             return true;
170         } finally {
171             lock.writeLock().unlock();
172         }
173     }
174 
175     public boolean containsPath(String path, String cacheKey) {
176         lock.writeLock().lock();
177         try {
178             if (!pathToIdMap.containsKey(path)) {
179                 return false;
180             }
181 
182             CacheItem<String> item = pathToIdMap.get(path);
183             if (item.isExpired() || !containsId(item.getItem(), cacheKey)) {
184                 pathToIdMap.remove(path);
185                 return false;
186             }
187 
188             return true;
189         } finally {
190             lock.writeLock().unlock();
191         }
192     }
193 
194     public CmisObject getById(String objectId, String cacheKey) {
195         lock.writeLock().lock();
196         try {
197             if (!containsId(objectId, cacheKey)) {
198                 return null;
199             }
200 
201             Map<String, CmisObject> item = objectMap.get(objectId).getItem();
202             return (item == null ? null : item.get(cacheKey));
203         } finally {
204             lock.writeLock().unlock();
205         }
206     }
207 
208     public CmisObject getByPath(String path, String cacheKey) {
209         lock.writeLock().lock();
210         try {
211             if (!containsPath(path, cacheKey)) {
212                 return null;
213             }
214 
215             CacheItem<String> item = pathToIdMap.get(path);
216             return getById(item.getItem(), cacheKey);
217         } finally {
218             lock.writeLock().unlock();
219         }
220     }
221 
222     public void put(CmisObject object, String cacheKey) {
223         // no object, no cache key - no cache
224         if ((object == null) || (cacheKey == null)) {
225             return;
226         }
227 
228         // no id - no cache
229         if (object.getId() == null) {
230             return;
231         }
232 
233         lock.writeLock().lock();
234         try {
235             // get cache key map
236             CacheItem<Map<String, CmisObject>> cacheKeyMap = objectMap.get(object.getId());
237             if (cacheKeyMap == null) {
238                 cacheKeyMap = new CacheItem<Map<String, CmisObject>>(new HashMap<String, CmisObject>(), cacheTtl);
239                 objectMap.put(object.getId(), cacheKeyMap);
240             }
241 
242             // put into id cache
243             Map<String, CmisObject> m = cacheKeyMap.getItem();
244             if (m != null) {
245                 m.put(cacheKey, object);
246             }
247 
248             // folders may have a path, use it!
249             String path = object.getPropertyValue(PropertyIds.PATH);
250             if (path != null) {
251                 pathToIdMap.put(path, new CacheItem<String>(object.getId(), pathToIdTtl));
252             }
253         } finally {
254             lock.writeLock().unlock();
255         }
256     }
257 
258     public void putPath(String path, CmisObject object, String cacheKey) {
259         if (path == null) {
260             return;
261         }
262 
263         lock.writeLock().lock();
264         try {
265             put(object, cacheKey);
266 
267             if ((object != null) && (object.getId() != null) && (cacheKey != null)) {
268                 pathToIdMap.put(path, new CacheItem<String>(object.getId(), pathToIdTtl));
269             }
270         } finally {
271             lock.writeLock().unlock();
272         }
273     }
274 
275     public void remove(String objectId) {
276         if(objectId == null) {
277             return;
278         }
279 
280         lock.writeLock().lock();
281         try {
282             objectMap.remove(objectId);
283         } finally {
284             lock.writeLock().unlock();
285         }
286     }
287 
288     public int getCacheSize() {
289         return this.cacheSize;
290     }
291 
292     // --- cache item ---
293 
294     private static class CacheItem<T> implements Serializable {
295 
296         private static final long serialVersionUID = 1L;
297 
298         private SoftReference<T> item;
299         private long timestamp;
300         private int ttl;
301 
302         public CacheItem(T item, int ttl) {
303             this.item = new SoftReference<T>(item);
304             timestamp = System.currentTimeMillis();
305             this.ttl = ttl;
306         }
307 
308         public synchronized boolean isExpired() {
309             if ((item == null) || (item.get() == null)) {
310                 return true;
311             }
312 
313             return (timestamp + ttl < System.currentTimeMillis());
314         }
315 
316         public synchronized T getItem() {
317             if (isExpired()) {
318                 item = null;
319                 return null;
320             }
321 
322             return item.get();
323         }
324 
325         private void writeObject(ObjectOutputStream out) throws IOException {
326             out.writeObject(isExpired() ? null : item.get());
327             out.writeLong(timestamp);
328             out.writeInt(ttl);
329         }
330 
331         private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
332             @SuppressWarnings("unchecked")
333             T object = (T) in.readObject();
334             timestamp = in.readLong();
335             ttl = in.readInt();
336 
337             if ((object != null) && (timestamp + ttl >= System.currentTimeMillis())) {
338                 this.item = new SoftReference<T>(object);
339             }
340         }
341     }
342 }