View Javadoc

1   /*
2    * $Id: PartitionedPersistentObjectStore.java 22507 2011-07-21 17:10:03Z stephen.fenech $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  package org.mule.util.store;
12  
13  import org.mule.api.MuleContext;
14  import org.mule.api.MuleRuntimeException;
15  import org.mule.api.context.MuleContextAware;
16  import org.mule.api.store.ObjectAlreadyExistsException;
17  import org.mule.api.store.ObjectDoesNotExistException;
18  import org.mule.api.store.ObjectStoreException;
19  import org.mule.api.store.PartitionableExpirableObjectStore;
20  import org.mule.config.i18n.CoreMessages;
21  import org.mule.config.i18n.Message;
22  import org.mule.util.FileUtils;
23  import org.mule.util.SerializationUtils;
24  
25  import java.io.File;
26  import java.io.FileInputStream;
27  import java.io.FileNotFoundException;
28  import java.io.FileOutputStream;
29  import java.io.IOException;
30  import java.io.Serializable;
31  import java.util.ArrayList;
32  import java.util.Arrays;
33  import java.util.Collections;
34  import java.util.Comparator;
35  import java.util.List;
36  import java.util.concurrent.TimeUnit;
37  
38  import org.apache.commons.lang.SerializationException;
39  
40  public class PartitionedPersistentObjectStore<T extends Serializable> extends
41      AbstractPartitionedObjectStore<T> implements MuleContextAware, PartitionableExpirableObjectStore<T>
42  {
43      MuleContext muleContext;
44      public static final String DEFAULT_OBJECT_STORE = "objectstore";
45      private static final String FILE_EXTENSION = ".obj";
46      private File storeDirectory;
47  
48      public PartitionedPersistentObjectStore()
49      {
50          super();
51      }
52  
53      public PartitionedPersistentObjectStore(MuleContext context)
54      {
55          super();
56          muleContext = context;
57      }
58  
59      @Override
60      public void open(String partitionName) throws ObjectStoreException
61      {
62          initStoreDirectory();
63          if (!storeDirectory.exists())
64          {
65              createStoreDirectory(storeDirectory);
66          }
67      }
68  
69      @Override
70      public void close(String partitionName) throws ObjectStoreException
71      {
72          // Nothing to do
73      }
74  
75      @Override
76      public boolean isPersistent()
77      {
78          return true;
79      }
80  
81      @Override
82      public boolean contains(Serializable key, String partitionName) throws ObjectStoreException
83      {
84          File storeFile = createStoreFile(key, partitionName);
85          return storeFile.exists();
86      }
87  
88      @Override
89      public void store(Serializable key, T value, String partitionName) throws ObjectStoreException
90      {
91          File outputFile = createStoreFile(key, partitionName);
92          ensureStoreDirectoryExists(outputFile);
93          boolean isNewFile;
94          try
95          {
96              isNewFile = outputFile.createNewFile();
97          }
98          catch (IOException e)
99          {
100             throw new ObjectStoreException(e);
101         }
102         if (!isNewFile)
103         {
104             throw new ObjectAlreadyExistsException();
105         }
106         serialize(value, outputFile);
107     }
108 
109     @Override
110     public T retrieve(Serializable key, String partitionName) throws ObjectStoreException
111     {
112         File file = createStoreFile(key, partitionName);
113         return deserialize(file);
114     }
115 
116     @Override
117     public T remove(Serializable key, String partitionName) throws ObjectStoreException
118     {
119         File storeFile = createStoreFile(key, partitionName);
120         T result = deserialize(storeFile);
121         deleteStoreFile(storeFile);
122 
123         return result;
124     }
125 
126     @Override
127     public List<Serializable> allKeys(String partitionName) throws ObjectStoreException
128     {
129         if (storeDirectory == null)
130         {
131             return Collections.emptyList();
132         }
133 
134         return collectAllKeys(partitionName);
135     }
136 
137     protected List<Serializable> collectAllKeys(String partitionName) throws ObjectStoreException
138     {
139         try
140         {
141             List<Serializable> keys = new ArrayList<Serializable>();
142             listStoredFiles(createStorePartition(partitionName), keys);
143 
144             return keys;
145         }
146         catch (ClassNotFoundException e)
147         {
148             String message = String.format("Could not restore from %1s", storeDirectory.getAbsolutePath());
149             throw new ObjectStoreException(CoreMessages.createStaticMessage(message));
150         }
151         catch (IOException e)
152         {
153             String message = String.format("Could not restore from %1s", storeDirectory.getAbsolutePath());
154             throw new ObjectStoreException(CoreMessages.createStaticMessage(message));
155         }
156     }
157 
158     protected void listStoredFiles(File directory, List<Serializable> keys)
159         throws IOException, ClassNotFoundException
160     {
161         File[] files = directory.listFiles();
162         if (files == null)
163         {
164             return;
165         }
166 
167         // sort the files so they are in the order in which their ids were generated
168         // in store()
169         Arrays.sort(files);
170 
171         for (int i = 0; i < files.length; i++)
172         {
173             if (files[i].isDirectory())
174             {
175                 listStoredFiles(files[i], keys);
176             }
177             else if (files[i].getName().endsWith(FILE_EXTENSION))
178             {
179                 String id = files[i].getCanonicalPath();
180 
181                 int beginIndex = storeDirectory.getCanonicalPath().length() + 1;
182                 int length = id.length() - FILE_EXTENSION.length();
183                 id = id.substring(beginIndex, length);
184 
185                 String partition = id.substring(0, id.indexOf(File.separator));
186                 id = id.substring(partition.length() + 1);
187 
188                 keys.add(id);
189             }
190         }
191     }
192 
193     @Override
194     public List<String> allPartitions() throws ObjectStoreException
195     {
196         File[] files = storeDirectory.listFiles();
197         if (files == null)
198         {
199             return new ArrayList<String>();
200         }
201 
202         // sort the files so they are in the order in which their ids were generated
203         // in store()
204         Arrays.sort(files);
205         List<String> partitions = new ArrayList<String>();
206 
207         for (int i = 0; i < files.length; i++)
208         {
209             if (files[i].isDirectory())
210             {
211                 partitions.add(files[i].getName());
212             }
213         }
214         return partitions;
215     }
216 
217     private void initStoreDirectory() throws ObjectStoreException
218     {
219         try
220         {
221             String workingDirectory = muleContext.getConfiguration().getWorkingDirectory();
222             String path = workingDirectory + File.separator + DEFAULT_OBJECT_STORE;
223             storeDirectory = FileUtils.newFile(path);
224         }
225         catch (MuleRuntimeException mre)
226         {
227             // FileUtils throws a MuleRuntimeException if something goes wrong when
228             // creating the
229             // path. To fully conform to the ObjectStore contract we cannot just let
230             // it bubble
231             // through but rather catch it and re-throw as ObjectStoreException
232             throw new ObjectStoreException(mre);
233         }
234     }
235 
236     protected synchronized void createStoreDirectory(File directory) throws ObjectStoreException
237     {
238         // To support concurrency we need to check if directory exists again inside
239         // synchronized method
240         if (!directory.exists() && !directory.mkdirs())
241         {
242             Message message = CoreMessages.failedToCreate("object store directory "
243                                                           + directory.getAbsolutePath());
244             throw new ObjectStoreException(message);
245         }
246     }
247 
248     protected File createStorePartition(String partitionName) throws ObjectStoreException
249     {
250         try
251         {
252             return FileUtils.newFile(storeDirectory, partitionName);
253         }
254         catch (MuleRuntimeException mre)
255         {
256             // FileUtils throws a MuleRuntimeException if something goes wrong when
257             // creating the
258             // path. To fully conform to the ObjectStore contract we cannot just let
259             // it bubble
260             // through but rather catch it and re-throw as ObjectStoreException
261             throw new ObjectStoreException(mre);
262         }
263     }
264 
265     protected File createStoreFile(Serializable key, String partitionName) throws ObjectStoreException
266     {
267         String filename = key + FILE_EXTENSION;
268         String path = partitionName + File.separator + filename;
269 
270         try
271         {
272             return FileUtils.newFile(storeDirectory, path);
273         }
274         catch (MuleRuntimeException mre)
275         {
276             // FileUtils throws a MuleRuntimeException if something goes wrong when
277             // creating the
278             // path. To fully conform to the ObjectStore contract we cannot just let
279             // it bubble
280             // through but rather catch it and re-throw as ObjectStoreException
281             throw new ObjectStoreException(mre);
282         }
283     }
284 
285     protected void ensureStoreDirectoryExists(File outputFile) throws ObjectStoreException
286     {
287         File directory = outputFile.getParentFile();
288         if (!directory.exists())
289         {
290             createStoreDirectory(directory);
291         }
292     }
293 
294     protected void serialize(T value, File outputFile) throws ObjectStoreException
295     {
296         try
297         {
298             FileOutputStream out = new FileOutputStream(outputFile);
299             SerializationUtils.serialize(value, out);
300         }
301         catch (SerializationException se)
302         {
303             throw new ObjectStoreException(se);
304         }
305         catch (FileNotFoundException fnfe)
306         {
307             throw new ObjectStoreException(fnfe);
308         }
309     }
310 
311     @SuppressWarnings("unchecked")
312     protected T deserialize(File file) throws ObjectStoreException
313     {
314         try
315         {
316             FileInputStream in = new FileInputStream(file);
317             return (T) SerializationUtils.deserialize(in, muleContext);
318         }
319         catch (SerializationException se)
320         {
321             throw new ObjectStoreException(se);
322         }
323         catch (FileNotFoundException fnfe)
324         {
325             throw new ObjectDoesNotExistException(fnfe);
326         }
327     }
328 
329     protected void deleteStoreFile(File file) throws ObjectStoreException
330     {
331         if (file.exists())
332         {
333             if (!file.delete())
334             {
335                 Message message = CoreMessages.createStaticMessage("Deleting " + file.getAbsolutePath()
336                                                                    + " failed");
337                 throw new ObjectStoreException(message);
338             }
339         }
340         else
341         {
342             throw new ObjectDoesNotExistException();
343         }
344     }
345 
346     public void open() throws ObjectStoreException
347     {
348         initStoreDirectory();
349         if (!storeDirectory.exists())
350         {
351             createStoreDirectory(storeDirectory);
352         }
353     }
354 
355     @Override
356     public void setMuleContext(MuleContext context)
357     {
358         muleContext = context;
359     }
360 
361     @Override
362     public void expire(int entryTTL, int maxEntries) throws ObjectStoreException
363     {
364         expire(entryTTL, maxEntries, DEFAULT_PARTITION);
365     }
366 
367     @Override
368     public void expire(int entryTTL, int maxEntries, String partitionName) throws ObjectStoreException
369     {
370         File partitionFolder = FileUtils.newFile(storeDirectory, partitionName);
371         File[] files = partitionFolder.listFiles();
372         if (files == null)
373         {
374             return;
375         }
376         Arrays.sort(files, new Comparator<File>()
377         {
378             public int compare(File f1, File f2)
379             {
380                 int result=Long.valueOf(f1.lastModified()).compareTo(f2.lastModified());
381                 if(result==0)
382                 {
383                     result=f1.getName().compareTo(f2.getName());
384                 }
385                 return result;
386             }
387         });
388         int startIndex = trimToMaxSize(files, maxEntries);
389 
390         final long now = System.currentTimeMillis();
391 
392         for (int i = startIndex; i < files.length; i++)
393         {
394             if (files[i].getName().endsWith(FILE_EXTENSION))
395             {
396                 Long lastModified = files[i].lastModified();
397                 if ((now - lastModified) >= entryTTL)
398                 {
399                     deleteStoreFile(files[i]);
400                 }
401                 else
402                 {
403                     break;
404                 }
405             }
406         }
407 
408     }
409 
410     private int trimToMaxSize(File[] files, int maxEntries) throws ObjectStoreException
411     {
412         if (maxEntries < 0)
413         {
414             return 0;
415         }
416         int expired = 0;
417         int excess = (files.length - maxEntries);
418         if (excess > 0)
419         {
420             for (int i = 0; i < excess; i++)
421             {
422                 deleteStoreFile(files[i]);
423                 expired++;
424             }
425         }
426         return expired;
427     }
428 
429     @Override
430     public void disposePartition(String partitionName) throws ObjectStoreException
431     {
432         File partitionFolder = FileUtils.newFile(storeDirectory, partitionName);
433         FileUtils.deleteQuietly(partitionFolder);
434     }
435 
436 }