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.StringTokenizer;
019    import java.io.ByteArrayOutputStream;
020    import java.io.ObjectOutputStream;
021    import java.io.ObjectInputStream;
022    import java.io.FileOutputStream;
023    import java.io.FileInputStream;
024    import java.io.BufferedInputStream;
025    import java.io.BufferedOutputStream;
026    import java.io.Serializable;
027    import java.io.IOException;
028    import java.io.File;
029    
030    /**
031     * tk.
032     * @version $Id: ShareableFileStash.java 155435 2005-02-26 13:17:27Z dirkv $
033     * @author Rodney Waldhoff
034     */
035    public class ShareableFileStash extends BaseStash implements Stash {
036        protected int _numDirectories = 10;
037        protected File _rootdir = null;
038        protected int _maxFilenameLength = 128;
039    
040        /**
041         * @param root the root directory to store objects in
042         * @param numdirs the number of directories to create under root (must be >0)
043         */
044        public ShareableFileStash(String root, int numdirs) {
045            _rootdir = new File(root);
046            _numDirectories = numdirs;
047        }
048    
049        /**
050         * @param root the root directory to store objects in
051         * @param numdirs the number of directories to create under root (must be >0)
052         */
053        public ShareableFileStash(File root, int numdirs) {
054            _rootdir = root;
055            _numDirectories = numdirs;
056        }
057    
058        /**
059         * @param root the root directory to store objects in
060         * @param numdirs the number of directories to create under root (must be >0)
061         * @param maxfilenamelength the maximum length filename to create
062         */
063        public ShareableFileStash(File root, int numdirs, int maxfilenamelength) {
064            _rootdir = root;
065            _numDirectories = numdirs;
066            _maxFilenameLength = maxfilenamelength;
067        }
068    
069        /**
070         * @param root the root directory to store objects in
071         * @param numdirs the number of directories to create under root (must be >0)
072         * @param maxfilenamelength the maximum length filename to create
073         */
074        public ShareableFileStash(String root, int numdirs, int maxfilenamelength) {
075            _rootdir = new File(root);
076            _numDirectories = numdirs;
077            _maxFilenameLength = maxfilenamelength;
078        }
079    
080        protected byte[] getSerializedForm(Serializable val) {
081            byte[] serform = null;
082            if(null == val) {
083                return null;
084            }
085            ByteArrayOutputStream byout = null;
086            ObjectOutputStream out = null;
087            try {
088                byout = new ByteArrayOutputStream();
089                out = new ObjectOutputStream(byout);
090                out.writeObject(val);
091                out.flush();
092                serform = byout.toByteArray();
093            } catch(IOException e) {
094                serform = null;
095            } finally {
096                try {
097                    byout.close();
098                } catch(Exception e) {
099                }
100                try {
101                    out.close();
102                } catch(Exception e) {
103                }
104            }
105            return serform;
106        }
107    
108        protected synchronized Serializable readFromFile(File file) {
109            ObjectInputStream oin = null;
110            try {
111                oin = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)));
112                return(Serializable)(oin.readObject());
113            } catch(Exception e) {
114                return null;
115            } finally {
116                try {
117                    oin.close();
118                } catch(Exception e) {
119                }
120            }
121        }
122    
123        protected File getFile(Serializable key, boolean mkdirs) {
124            String keystr = key.toString();
125            char[] chars = keystr.toCharArray();
126            StringBuffer buf = new StringBuffer();
127            File parent = new File(_rootdir,String.valueOf(Math.abs(keystr.hashCode()%_numDirectories)));
128            for(int i=0;i<chars.length;i++) {
129                if((chars[i] >= 'a' && chars[i] <= 'z') ||
130                   (chars[i] >= 'A' && chars[i] <= 'Z') ||
131                   (chars[i] >= '0' && chars[i] <= '9')) {
132                    if(buf.length() >= _maxFilenameLength) {
133                        buf.append("_");
134                        parent = new File(parent,buf.toString());
135                        buf.setLength(0);
136                    }
137                    buf.append(chars[i]);
138                } else {
139                    if(buf.length() + 5 >= _maxFilenameLength) {
140                        buf.append("_");
141                        parent = new File(parent,buf.toString());
142                        buf.setLength(0);
143                    }
144                    buf.append("_");
145                    buf.append(Integer.toHexString((int)chars[i]));
146                }
147            }
148            if(buf.length() == 0) {
149                buf.append("_");
150            }
151            if(mkdirs) {
152                parent.mkdirs();
153            }
154            return new File(parent,buf.toString());
155        }
156    
157        public int canStore(Serializable key, Serializable val, Long expiresAt, Long cost, Serializable group, byte[] serialized) {
158            if(null == serialized) {
159                serialized = getSerializedForm(val);
160            }
161            if(null == serialized) {
162                return Stash.NO;
163            } else {
164                return Stash.YES;
165            }
166        }
167    
168        public boolean store(Serializable key, Serializable val, Long expiresAt, Long cost, Serializable group, byte[] serialized) {
169            if(null == serialized) {
170                serialized = getSerializedForm(val);
171            }
172            if(null == serialized) {
173                return false;
174            }
175    
176            File cachefile = getFile(key,true);
177            if(null == cachefile) {
178                return false;
179            }
180    
181            BufferedOutputStream fout = null;
182            try {
183                fout = new BufferedOutputStream(new FileOutputStream(cachefile));
184                fout.write(serialized);
185                fout.flush();
186            } catch(Exception e) {
187                try {
188                    fout.close();
189                } catch(Exception ex) {
190                }
191                fout = null;
192                try {
193                    cachefile.delete();
194                } catch(Exception ex) {
195                }
196                return false;
197            } finally {
198                try {
199                    fout.close();
200                } catch(Exception e) {
201                }
202            }
203            return true;
204        }
205    
206        public Serializable retrieve(Serializable key) {
207            File cachefile = getFile(key,false);
208            if(cachefile.exists()) {
209                return readFromFile(cachefile);
210            } else {
211                return null;
212            }
213        }
214    
215        public boolean contains(Serializable key) {
216            File cachefile = getFile(key,false);
217            return cachefile.exists();
218        }
219    
220        public float capacity() {
221            return 0;
222        }
223    
224        public void clear(Serializable key) {
225            File cachefile = getFile(key,false);
226            cachefile.delete();
227        }
228    
229        public synchronized void clear() {
230            File f = getMoveToLoc();
231            try {
232                f.getParentFile().mkdirs();
233            } catch(NullPointerException e) {
234                // ignored
235            }
236            _rootdir.renameTo(f);
237            _rootdir.mkdirs();
238            Thread t = new Thread(new RecursiveFileDeleter(f));
239            t.start();
240        }
241    
242        public boolean wantsSerializedForm() {
243            return true;
244        }
245    
246        public void unsetCache() {
247        }
248    
249        public void setCache(Cache c) {
250        }
251    
252        protected File getMoveToLoc() {
253            File parent = _rootdir.getAbsoluteFile().getParentFile();
254            for(int i=0;i<100;i++) {
255                String fname = String.valueOf(System.currentTimeMillis() % 100000000L);
256                File f = new File(parent,fname);
257                if(!f.exists()) {
258                    return f;
259                }
260                try {
261                    Thread.currentThread().sleep(17);
262                } catch(Exception e) {
263                    // ignored
264                }
265            }
266            throw new RuntimeException("Couldn't create a temp file to move the shareable file stash to.");
267        }
268    }
269    
270    class RecursiveFileDeleter implements Runnable {
271        private File _root = null;
272    
273        public RecursiveFileDeleter(File root) {
274            _root = root;
275        }
276    
277        public void run() {
278            boolean success = recursiveDelete(_root);
279            if(!success) {
280                System.err.println("Unable to fully delete the file at \"" + _root + "\". Please delete it manually.");
281            }
282        }
283    
284        boolean recursiveDelete(File f) {
285            if(f.isDirectory()) {
286                File[] files = f.listFiles();
287                for(int i=0;i<files.length;i++) {
288                    if(files[i].isFile()) {
289                        if(!files[i].delete()) {
290                            return false;
291                        }
292                    } else {
293                        if(!recursiveDelete(files[i])) {
294                            return false;
295                        }
296                    }
297                }
298            }
299            return f.delete();
300        }
301    }
302    
303    
304