001    /*
002     * Copyright 2001-2004 The Apache Software Foundation
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.apache.commons.cache;
017    
018    import java.util.HashMap;
019    import java.util.Iterator;
020    import java.util.ArrayList;
021    import java.util.StringTokenizer;
022    import java.io.ByteArrayOutputStream;
023    import java.io.ObjectOutputStream;
024    import java.io.ObjectInputStream;
025    import java.io.FileOutputStream;
026    import java.io.FileInputStream;
027    import java.io.BufferedInputStream;
028    import java.io.BufferedOutputStream;
029    import java.io.Serializable;
030    import java.io.IOException;
031    import java.io.File;
032    
033    /**
034     * tk.
035     * @version $Id: FileStash.java 155435 2005-02-26 13:17:27Z dirkv $
036     * @author Rodney Waldhoff
037     */
038    public class FileStash extends BaseStash implements Stash {
039      public transient static final long DEFAULT_MAX_OBJS;
040      static {
041        int defaultMaxObjs = -1;
042        try {
043          defaultMaxObjs = Integer.parseInt(System.getProperty("org.apache.commons.cache.FileStash.max-objs","-1"));
044        } catch(Exception e) {
045          defaultMaxObjs = -1;
046        }
047        DEFAULT_MAX_OBJS = defaultMaxObjs;
048      }
049    
050      public transient static final long DEFAULT_MAX_BYTES;
051      static {
052        int defaultMaxBytes = -1;
053        try {
054          defaultMaxBytes = Integer.parseInt(System.getProperty("org.apache.commons.cache.FileStash.max-bytes","-1"));
055        } catch(Exception e) {
056          defaultMaxBytes = -1;
057        }
058        DEFAULT_MAX_BYTES = defaultMaxBytes;
059      }
060    
061      public transient static final File[] DEFAULT_TEMP_DIRS;
062      static {
063        ArrayList v = new ArrayList();
064        String rootdirstr = System.getProperty("org.apache.commons.cache.FileStash.root-cache-dir");
065        File rootdir = null;
066        if(null == rootdir) {
067          DEFAULT_TEMP_DIRS = null;
068        } else {
069          rootdir = new File(rootdirstr);
070          int branchingFactor = 10;
071          try {
072            branchingFactor = Integer.parseInt(System.getProperty("org.apache.commons.cache.FileStash.num-cache-dirs","10"));
073          } catch(Exception e) {
074            branchingFactor = 10;
075          }
076          if(branchingFactor < 0) {
077            branchingFactor = 10;
078          } else if(branchingFactor==0) {
079            v.add(rootdir);
080          } else {
081            for(int i=0;i<branchingFactor;i++) {
082              v.add(new File(rootdir,String.valueOf(i)));
083            }
084          }
085          DEFAULT_TEMP_DIRS = (File[])(v.toArray(new File[0]));
086        }
087      }
088    
089      public transient static final String DEFAULT_FILE_PREFIX;
090      static {
091        DEFAULT_FILE_PREFIX = System.getProperty("org.apache.commons.cached.FileStash.default-file-prefix","cache");
092      }
093    
094    
095      public transient static final String DEFAULT_FILE_SUFFIX;
096      static {
097        DEFAULT_FILE_SUFFIX = System.getProperty("org.apache.commons.cache.FileStash.default-file-suffix",".ser");
098      }
099    
100      protected HashMap _hash = null;
101      protected long _maxObjs = DEFAULT_MAX_OBJS;
102      protected long _maxBytes = DEFAULT_MAX_BYTES;
103      protected Cache _cache = null;
104      protected long _curBytes = 0;
105      protected File[] _tempFileDirs = null;
106      protected String _tempFilePrefix = DEFAULT_FILE_PREFIX;
107      protected String _tempFileSuffix = DEFAULT_FILE_SUFFIX;
108      protected boolean _cleanupfiles = false;
109    
110      public FileStash() {
111        this(DEFAULT_MAX_BYTES,DEFAULT_MAX_OBJS,DEFAULT_TEMP_DIRS,false);
112      }
113    
114      public FileStash(long maxbytes) {
115        this(maxbytes,DEFAULT_MAX_OBJS,DEFAULT_TEMP_DIRS,false);
116      }
117    
118      public FileStash(long maxbytes, long maxobjs) {
119        this(maxbytes,maxobjs,DEFAULT_TEMP_DIRS,false);
120      }
121    
122      public FileStash(long maxbytes, long maxobjs, File[] tempdirs, boolean cleanup) {
123        _maxObjs = maxobjs;
124        _maxBytes = maxbytes;
125        _tempFileDirs = tempdirs;
126        _hash = new HashMap();
127        _cleanupfiles = cleanup;
128      }
129    
130      public FileStash(long maxbytes, long maxobjs, String rootdir, int numdirs) {
131        this(maxbytes,maxobjs,(null == rootdir ? ((File)null) : new File(rootdir)),numdirs,false);
132      }
133    
134      public FileStash(long maxbytes, long maxobjs, String rootdir, int numdirs, boolean cleanup) {
135        this(maxbytes,maxobjs,(null == rootdir ? ((File)null) : new File(rootdir)),numdirs,cleanup);
136      }
137    
138      public FileStash(long maxbytes, long maxobjs, File rootdir, int numdirs) {
139        this(maxbytes,maxobjs,rootdir,numdirs,false);
140      }
141    
142      public FileStash(long maxbytes, long maxobjs, File rootdir, int numdirs, boolean cleanup) {
143        _maxObjs = maxobjs;
144        _maxBytes = maxbytes;
145        ArrayList v = new ArrayList();
146        if(null == rootdir) {
147          _tempFileDirs = null;
148        } else {
149          if(numdirs < 0) {
150            numdirs = 10;
151          } else if(numdirs==0) {
152            v.add(rootdir);
153          } else {
154            for(int i=0;i<numdirs;i++) {
155              v.add(new File(rootdir,String.valueOf(i)));
156            }
157          }
158          _tempFileDirs = (File[])(v.toArray(new File[0]));
159        }
160        _cleanupfiles = cleanup;
161        _hash = new HashMap();
162      }
163    
164      protected byte[] getSerializedForm(Serializable val) {
165        byte[] serform = null;
166        if(null == val) { return null; }
167        ByteArrayOutputStream byout = null;
168        ObjectOutputStream out = null;
169        try {
170          byout = new ByteArrayOutputStream();
171          out = new ObjectOutputStream(byout);
172          out.writeObject(val);
173          out.flush();
174          serform = byout.toByteArray();
175        } catch(IOException e) {
176          serform = null;
177        } finally {
178          try { byout.close(); } catch(Exception e) { }
179          try { out.close(); } catch(Exception e) { }
180        }
181        return serform;
182      }
183    
184      protected synchronized Serializable readFromFile(File file) {
185        ObjectInputStream oin = null;
186        try {
187          oin = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)));
188          return (Serializable)(oin.readObject());
189        } catch(Exception e) {
190          return null;
191        } finally {
192          try { oin.close(); } catch(Exception e) { }
193        }
194      }
195    
196      public synchronized int canStore(Serializable key, Serializable val, Long expiresAt, Long cost, Serializable group, byte[] serialized) {
197        if(null == serialized) {
198          serialized = getSerializedForm(val);
199        }
200        if(null == serialized) {
201          return Stash.NO;
202        } else if(_maxBytes != -1 && serialized.length > _maxBytes) {
203          return Stash.NO_NOT_STORABLE;
204        } else if(_maxBytes != -1 && _curBytes + serialized.length > _maxBytes) {
205          return Stash.NO_FULL;
206        } else if(_maxObjs != -1 && _hash.size() > _maxObjs) {
207          return Stash.NO_FULL;
208        } else {
209          return Stash.YES;
210        }
211      }
212    
213      protected int _tempfileCounter = 0;
214    
215      protected File getTempfile() {
216        File cachefile = null;
217        try {
218          File tempdir = null;
219          if(_tempFileDirs != null && _tempFileDirs.length > 0) {
220            tempdir = _tempFileDirs[_tempfileCounter++%_tempFileDirs.length];
221            if(!tempdir.exists()) {
222              if(_cleanupfiles) { tempdir.deleteOnExit(); }
223              tempdir.mkdirs();
224              tempdir.mkdir();
225            }
226          }
227          cachefile = File.createTempFile(_tempFilePrefix,_tempFileSuffix,tempdir);
228          if(_cleanupfiles) { cachefile.deleteOnExit(); }
229        } catch(Exception e) {
230          return null;
231        }
232        return cachefile;
233      }
234    
235      public synchronized boolean store(Serializable key, Serializable val, Long expiresAt, Long cost, Serializable group, byte[] serialized) {
236        if(null == serialized) {
237          serialized = getSerializedForm(val);
238        }
239        if(null == serialized) {
240          return false;
241        }
242    
243        File cachefile = getTempfile();
244        if(null == cachefile) {
245          return false;
246        }
247    
248        BufferedOutputStream fout = null;
249        try {
250          fout = new BufferedOutputStream(new FileOutputStream(cachefile));
251          fout.write(serialized);
252          fout.flush();
253        } catch(Exception e) {
254          try { fout.close(); } catch(Exception ex) { }
255          fout = null;
256          try { cachefile.delete(); } catch(Exception ex) { }
257          return false;
258        } finally {
259          try { fout.close(); } catch(Exception e) { }
260        }
261    
262        Object oldobj = null;
263        try {
264          oldobj = _hash.put(key,new CachedObjectInfoImpl(cachefile,expiresAt,new Long(serialized.length)));
265        } catch(Exception e) {
266          try { cachefile.delete(); } catch(Exception ex)  { }
267          return false;
268        } finally {
269          if(null != oldobj && oldobj instanceof CachedObjectInfo) {
270            CachedObjectInfo oldcachedobj = (CachedObjectInfo)oldobj;
271            try {
272              _curBytes -= oldcachedobj.getCost().longValue();
273            } catch(NullPointerException ex) {
274              // ignored
275            }
276            try {
277              File f = (File)(oldcachedobj.getKey());
278              f.delete();
279            } catch(Exception ex) {
280                ex.printStackTrace();
281              // ignored
282            }
283          }
284        }
285        _curBytes += serialized.length;
286        return true;
287      }
288    
289      public Serializable retrieve(Serializable key) {
290        // grab a lock on the cache first, since it may attempt to grab a lock on this
291        synchronized(_cache) {
292          synchronized(this) {
293            CachedObjectInfo info = (CachedObjectInfo)(_hash.get(key));
294            if(null != info) {
295              Long expiry = info.getExpirationTs();
296              if(null != expiry) {
297                if(expiry.longValue() < System.currentTimeMillis()) {
298                  _cache.clear(key);
299                  return null;
300                } else {
301                  return readFromFile((File)(info.getKey()));
302                }
303              } else {
304                return readFromFile((File)(info.getKey()));
305              }
306            } else {
307              return null;
308            }
309          }
310        }
311      }
312    
313      public synchronized boolean contains(Serializable key) {
314        return _hash.containsKey(key);
315      }
316    
317      public synchronized float capacity() {
318        float objcount = 0;
319        if(_maxObjs > 0) {
320           objcount = (((float)_hash.size())/((float)_maxObjs));
321        }
322        float bytecount = 0;
323        if(_maxBytes > 0) {
324          bytecount = (((float)_curBytes)/((float)_maxBytes));
325        }
326        return (objcount > bytecount) ? objcount : bytecount;
327      }
328    
329      public synchronized void clear(Serializable key) {
330        CachedObjectInfo obj = (CachedObjectInfo)(_hash.remove(key));
331        if(null != obj) {
332          _curBytes -= obj.getCost().longValue();
333          ((File)(obj.getKey())).delete();
334        }
335      }
336    
337      public synchronized void clear() {
338        Iterator it = _hash.keySet().iterator();
339        while(it.hasNext()) {
340          try {
341            CachedObjectInfo obj = (CachedObjectInfo)(it.next());
342            ((File)(obj.getKey())).delete();
343          } catch(Exception e) {
344              e.printStackTrace();
345            // ignored
346          }
347        }
348        _hash.clear();
349        _curBytes = 0;
350      }
351    
352      public void setCache(Cache c) {
353        if(null != _cache) {
354          Object mutex = _cache;
355          synchronized(mutex) {
356            synchronized(this) {
357              unsetCache();
358              _cache = c;
359            }
360          }
361        } else {
362          _cache = c;
363        }
364      }
365    
366      public void unsetCache() {
367        if(null != _cache) {
368          Object mutex = _cache;
369          synchronized(mutex) {
370            clear();
371            _cache = null;
372          }
373        }
374      }
375    
376      public boolean wantsSerializedForm() {
377        return true;
378      }
379    }