View Javadoc

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  import org.apache.chemistry.opencmis.commons.SessionParameterDefaults;
36  
37  /**
38   * Synchronized cache implementation. The cache is limited to a specific size of
39   * entries and works in a LRU mode.
40   */
41  public class CacheImpl implements Cache {
42  
43      private static final long serialVersionUID = 1L;
44  
45      private static final float HASHTABLE_LOAD_FACTOR = 0.75f;
46  
47      private int cacheSize;
48      private int cacheTtl;
49      private int pathToIdSize;
50      private int pathToIdTtl;
51  
52      private LinkedHashMap<String, CacheItem<Map<String, CmisObject>>> objectMap;
53      private LinkedHashMap<String, CacheItem<String>> pathToIdMap;
54  
55      private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
56  
57      /**
58       * Default constructor.
59       */
60      public CacheImpl() {
61      }
62  
63      @Override
64      public void initialize(Session session, Map<String, String> parameters) {
65          assert parameters != null;
66  
67          lock.writeLock().lock();
68          try {
69              // cache size
70              try {
71                  cacheSize = Integer.valueOf(parameters.get(SessionParameter.CACHE_SIZE_OBJECTS));
72                  if (cacheSize < 0) {
73                      cacheSize = 0;
74                  }
75              } catch (Exception e) {
76                  cacheSize = SessionParameterDefaults.CACHE_SIZE_OBJECTS;
77              }
78  
79              // cache time-to-live
80              try {
81                  cacheTtl = Integer.valueOf(parameters.get(SessionParameter.CACHE_TTL_OBJECTS));
82                  if (cacheTtl < 0) {
83                      cacheTtl = SessionParameterDefaults.CACHE_TTL_OBJECTS;
84                  }
85              } catch (Exception e) {
86                  cacheTtl = SessionParameterDefaults.CACHE_TTL_OBJECTS;
87              }
88  
89              // path-to-id size
90              try {
91                  pathToIdSize = Integer.valueOf(parameters.get(SessionParameter.CACHE_SIZE_PATHTOID));
92                  if (pathToIdSize < 0) {
93                      pathToIdSize = 0;
94                  }
95              } catch (Exception e) {
96                  pathToIdSize = SessionParameterDefaults.CACHE_SIZE_PATHTOID;
97              }
98  
99              // path-to-id time-to-live
100             try {
101                 pathToIdTtl = Integer.valueOf(parameters.get(SessionParameter.CACHE_TTL_PATHTOID));
102                 if (pathToIdTtl < 0) {
103                     pathToIdTtl = SessionParameterDefaults.CACHE_TTL_PATHTOID;
104                 }
105             } catch (Exception e) {
106                 pathToIdTtl = SessionParameterDefaults.CACHE_TTL_PATHTOID;
107             }
108 
109             initializeInternals();
110         } finally {
111             lock.writeLock().unlock();
112         }
113     }
114 
115     /**
116      * Sets up the internal objects.
117      */
118     private void initializeInternals() {
119         lock.writeLock().lock();
120         try {
121             // object cache
122             int cacheHashTableCapacity = (int) Math.ceil(cacheSize / HASHTABLE_LOAD_FACTOR) + 1;
123 
124             final int cs = cacheSize;
125 
126             objectMap = new LinkedHashMap<String, CacheItem<Map<String, CmisObject>>>(cacheHashTableCapacity,
127                     HASHTABLE_LOAD_FACTOR) {
128 
129                 private static final long serialVersionUID = 1L;
130 
131                 @Override
132                 protected boolean removeEldestEntry(Map.Entry<String, CacheItem<Map<String, CmisObject>>> eldest) {
133                     return size() > cs;
134                 }
135             };
136 
137             // path-to-id mapping
138             int pathtoidHashTableCapacity = (int) Math.ceil(pathToIdSize / HASHTABLE_LOAD_FACTOR) + 1;
139 
140             final int ptis = pathToIdSize;
141 
142             pathToIdMap = new LinkedHashMap<String, CacheItem<String>>(pathtoidHashTableCapacity, HASHTABLE_LOAD_FACTOR) {
143 
144                 private static final long serialVersionUID = 1L;
145 
146                 @Override
147                 protected boolean removeEldestEntry(Map.Entry<String, CacheItem<String>> eldest) {
148                     return size() > ptis;
149                 }
150             };
151         } finally {
152             lock.writeLock().unlock();
153         }
154     }
155 
156     @Override
157     public void clear() {
158         initializeInternals();
159     }
160 
161     @Override
162     public boolean containsId(String objectId, String cacheKey) {
163         lock.writeLock().lock();
164         try {
165             if (!objectMap.containsKey(objectId)) {
166                 return false;
167             }
168 
169             CacheItem<Map<String, CmisObject>> item = objectMap.get(objectId);
170             if (item.isExpired()) {
171                 objectMap.remove(objectId);
172                 return false;
173             }
174 
175             return true;
176         } finally {
177             lock.writeLock().unlock();
178         }
179     }
180 
181     @Override
182     public boolean containsPath(String path, String cacheKey) {
183         lock.writeLock().lock();
184         try {
185             if (!pathToIdMap.containsKey(path)) {
186                 return false;
187             }
188 
189             CacheItem<String> item = pathToIdMap.get(path);
190             if (item.isExpired() || !containsId(item.getItem(), cacheKey)) {
191                 pathToIdMap.remove(path);
192                 return false;
193             }
194 
195             return true;
196         } finally {
197             lock.writeLock().unlock();
198         }
199     }
200 
201     @Override
202     public CmisObject getById(String objectId, String cacheKey) {
203         lock.writeLock().lock();
204         try {
205             if (!containsId(objectId, cacheKey)) {
206                 return null;
207             }
208 
209             Map<String, CmisObject> item = objectMap.get(objectId).getItem();
210             return item == null ? null : item.get(cacheKey);
211         } finally {
212             lock.writeLock().unlock();
213         }
214     }
215 
216     @Override
217     public CmisObject getByPath(String path, String cacheKey) {
218         lock.writeLock().lock();
219         try {
220             if (!containsPath(path, cacheKey)) {
221                 return null;
222             }
223 
224             CacheItem<String> item = pathToIdMap.get(path);
225             return getById(item.getItem(), cacheKey);
226         } finally {
227             lock.writeLock().unlock();
228         }
229     }
230 
231     @Override
232     public String getObjectIdByPath(String path) {
233         lock.writeLock().lock();
234         try {
235             CacheItem<String> item = pathToIdMap.get(path);
236             if (item == null) {
237                 return null;
238             }
239             if (item.isExpired()) {
240                 pathToIdMap.remove(path);
241                 return null;
242             }
243 
244             return item.getItem();
245         } finally {
246             lock.writeLock().unlock();
247         }
248     }
249 
250     @Override
251     public void put(CmisObject object, String cacheKey) {
252         // no object, no cache key - no cache
253         if ((object == null) || (cacheKey == null)) {
254             return;
255         }
256 
257         // no id - no cache
258         if (object.getId() == null) {
259             return;
260         }
261 
262         lock.writeLock().lock();
263         try {
264             // get cache key map
265             CacheItem<Map<String, CmisObject>> cacheKeyMap = objectMap.get(object.getId());
266             if (cacheKeyMap == null) {
267                 cacheKeyMap = new CacheItem<Map<String, CmisObject>>(new HashMap<String, CmisObject>(), cacheTtl);
268                 objectMap.put(object.getId(), cacheKeyMap);
269             }
270 
271             // put into id cache
272             Map<String, CmisObject> m = cacheKeyMap.getItem();
273             if (m != null) {
274                 m.put(cacheKey, object);
275             }
276 
277             // folders may have a path, use it!
278             String path = object.getPropertyValue(PropertyIds.PATH);
279             if (path != null) {
280                 pathToIdMap.put(path, new CacheItem<String>(object.getId(), pathToIdTtl));
281             }
282         } finally {
283             lock.writeLock().unlock();
284         }
285     }
286 
287     @Override
288     public void putPath(String path, CmisObject object, String cacheKey) {
289         if (path == null) {
290             return;
291         }
292 
293         lock.writeLock().lock();
294         try {
295             put(object, cacheKey);
296 
297             if ((object != null) && (object.getId() != null) && (cacheKey != null)) {
298                 pathToIdMap.put(path, new CacheItem<String>(object.getId(), pathToIdTtl));
299             }
300         } finally {
301             lock.writeLock().unlock();
302         }
303     }
304 
305     @Override
306     public void remove(String objectId) {
307         if (objectId == null) {
308             return;
309         }
310 
311         lock.writeLock().lock();
312         try {
313             objectMap.remove(objectId);
314         } finally {
315             lock.writeLock().unlock();
316         }
317     }
318 
319     @Override
320     public void removePath(String path) {
321         if (path == null) {
322             return;
323         }
324 
325         lock.writeLock().lock();
326         try {
327             pathToIdMap.remove(path);
328         } finally {
329             lock.writeLock().unlock();
330         }
331     }
332 
333     @Override
334     public int getCacheSize() {
335         return this.cacheSize;
336     }
337 
338     // --- cache item ---
339 
340     private static class CacheItem<T> implements Serializable {
341 
342         private static final long serialVersionUID = 1L;
343 
344         private SoftReference<T> item;
345         private long timestamp;
346         private int ttl;
347 
348         public CacheItem(T item, int ttl) {
349             this.item = new SoftReference<T>(item);
350             timestamp = System.currentTimeMillis();
351             this.ttl = ttl;
352         }
353 
354         public synchronized boolean isExpired() {
355             if ((item == null) || (item.get() == null)) {
356                 return true;
357             }
358 
359             return timestamp + ttl < System.currentTimeMillis();
360         }
361 
362         public synchronized T getItem() {
363             if (isExpired()) {
364                 item = null;
365                 return null;
366             }
367 
368             return item.get();
369         }
370 
371         private void writeObject(ObjectOutputStream out) throws IOException {
372             out.writeObject(isExpired() ? null : item.get());
373             out.writeLong(timestamp);
374             out.writeInt(ttl);
375         }
376 
377         private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
378             @SuppressWarnings("unchecked")
379             T object = (T) in.readObject();
380             timestamp = in.readLong();
381             ttl = in.readInt();
382 
383             if ((object != null) && (timestamp + ttl >= System.currentTimeMillis())) {
384                 this.item = new SoftReference<T>(object);
385             }
386         }
387     }
388 }