View Javadoc

1   /*
2    * $Id: QueuePersistenceObjectStore.java 21762 2011-05-03 01:29:28Z mike.schilling $
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.ListableObjectStore;
17  import org.mule.api.store.ObjectDoesNotExistException;
18  import org.mule.api.store.ObjectStore;
19  import org.mule.api.store.ObjectStoreException;
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  import org.mule.util.queue.QueueKey;
25  
26  import java.io.File;
27  import java.io.FileInputStream;
28  import java.io.FileNotFoundException;
29  import java.io.FileOutputStream;
30  import java.io.IOException;
31  import java.io.Serializable;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.Collections;
35  import java.util.List;
36  
37  import org.apache.commons.lang.SerializationException;
38  
39  /**
40   * <p>
41   * This is an {@link ObjectStore} implementation that is to be used to persist
42   * messages on Mule's internal queues. Note that this is a specialized implementation
43   * of the {@link ObjectStore} interface which hard-codes the location of the
44   * persistence folder to <code>$MULE_HOME/.mule/queuestore</code>. It also breaks the
45   * contract defined in the {@link ObjectStore} javadocs as it does not load and
46   * return the stored object when deleting and entry - the calling code does not care
47   * about the removed object anyway.
48   * </p>
49   * <p>
50   * This implementation uses <a href=
51   * "http://download.oracle.com/javase/1.5.0/docs/guide/serialization/spec/serialTOC.html"
52   * > Java serialization</a> to implement persistence.
53   * </p>
54   */
55  public class QueuePersistenceObjectStore<T extends Serializable> extends AbstractObjectStore<T>
56      implements ListableObjectStore<T>, MuleContextAware
57  {
58      /**
59       * The default queueStore directory for persistence
60       */
61      public static final String DEFAULT_QUEUE_STORE = "queuestore";
62  
63      private static final String FILE_EXTENSION = ".msg";
64  
65      private MuleContext muleContext;
66  
67      /**
68       * This is the base directory into which all queues will be persisted
69       */
70      private File storeDirectory;
71  
72      /**
73       * Default constructor for Spring.
74       */
75      public QueuePersistenceObjectStore()
76      {
77          super();
78      }
79  
80      public QueuePersistenceObjectStore(MuleContext context)
81      {
82          super();
83          muleContext = context;
84      }
85  
86      /**
87       * {@inheritDoc}
88       */
89      public boolean isPersistent()
90      {
91          return true;
92      }
93  
94      public void open() throws ObjectStoreException
95      {
96          initStoreDirectory();
97          if (!storeDirectory.exists())
98          {
99              createStoreDirectory(storeDirectory);
100         }
101     }
102 
103     private void initStoreDirectory() throws ObjectStoreException
104     {
105         try
106         {
107             String workingDirectory = muleContext.getConfiguration().getWorkingDirectory();
108             String path = workingDirectory + File.separator + DEFAULT_QUEUE_STORE;
109             storeDirectory = FileUtils.newFile(path);
110         }
111         catch (MuleRuntimeException mre)
112         {
113             // FileUtils throws a MuleRuntimeException if something goes wrong when creating the
114             // path. To fully conform to the ObjectStore contract we cannot just let it bubble
115             // through but rather catch it and re-throw as ObjectStoreException
116             throw new ObjectStoreException(mre);
117         }
118     }
119 
120     protected synchronized void createStoreDirectory(File directory) throws ObjectStoreException
121     {
122         // To support concurrency we need to check if directory exists again inside
123         // synchronized method
124         if (!directory.exists() && !directory.mkdirs())
125         {
126             Message message = CoreMessages.failedToCreate("queue store store directory " + directory.getAbsolutePath());
127             throw new ObjectStoreException(message);
128         }
129     }
130 
131     public void close() throws ObjectStoreException
132     {
133         // Nothing to do
134     }
135 
136     public List<Serializable> allKeys() throws ObjectStoreException
137     {
138         if (storeDirectory == null)
139         {
140             return Collections.emptyList();
141         }
142 
143         return collectAllKeys();
144     }
145 
146     protected List<Serializable> collectAllKeys() throws ObjectStoreException
147     {
148         try
149         {
150             List<Serializable> keys = new ArrayList<Serializable>();
151             listStoredFiles(storeDirectory, keys);
152 
153             if (logger.isDebugEnabled())
154             {
155                 logger.debug("Restore retrieved " + keys.size() + " objects");
156             }
157 
158             return keys;
159         }
160         catch (ClassNotFoundException e)
161         {
162             String message = String.format("Could not restore from %1s", storeDirectory.getAbsolutePath());
163             throw new ObjectStoreException(CoreMessages.createStaticMessage(message));
164         }
165         catch (IOException e)
166         {
167             String message = String.format("Could not restore from %1s", storeDirectory.getAbsolutePath());
168             throw new ObjectStoreException(CoreMessages.createStaticMessage(message));
169         }
170     }
171 
172     protected void listStoredFiles(File directory, List<Serializable> keys) throws IOException, ClassNotFoundException
173     {
174         File[] files = directory.listFiles();
175         if (files == null)
176         {
177             return;
178         }
179 
180         // sort the files so they are in the order in which their ids were generated in store()
181         Arrays.sort(files);
182 
183         for (int i = 0; i < files.length; i++)
184         {
185             if (files[i].isDirectory())
186             {
187                 listStoredFiles(files[i], keys);
188             }
189             else if (files[i].getName().endsWith(FILE_EXTENSION))
190             {
191                 String id = files[i].getCanonicalPath();
192 
193                 int beginIndex = storeDirectory.getCanonicalPath().length() + 1;
194                 int length = id.length() - FILE_EXTENSION.length();
195                 id = id.substring(beginIndex, length);
196 
197                 String queue = id.substring(0, id.indexOf(File.separator));
198                 id = id.substring(queue.length() + 1);
199 
200                 keys.add(new QueueKey(queue, id));
201             }
202         }
203     }
204 
205     @Override
206     protected boolean doContains(Serializable key) throws ObjectStoreException
207     {
208         File storeFile = createStoreFile(key);
209         return storeFile.exists();
210     }
211 
212     @Override
213     protected void doStore(Serializable key, T value) throws ObjectStoreException
214     {
215         File outputFile = createStoreFile(key);
216         ensureStoreDirectoryExists(outputFile);
217         serialize(value, outputFile);
218     }
219 
220     protected void ensureStoreDirectoryExists(File outputFile) throws ObjectStoreException
221     {
222         File directory = outputFile.getParentFile();
223         if (!directory.exists())
224         {
225             createStoreDirectory(directory);
226         }
227     }
228 
229     protected void serialize(T value, File outputFile) throws ObjectStoreException
230     {
231         try
232         {
233             FileOutputStream out = new FileOutputStream(outputFile);
234             SerializationUtils.serialize(value, out);
235         }
236         catch (SerializationException se)
237         {
238             throw new ObjectStoreException(se);
239         }
240         catch (FileNotFoundException fnfe)
241         {
242             throw new ObjectStoreException(fnfe);
243         }
244     }
245 
246     @Override
247     protected T doRetrieve(Serializable key) throws ObjectStoreException
248     {
249         File file = createStoreFile(key);
250         return deserialize(file);
251     }
252 
253     protected File createStoreFile(Serializable key) throws ObjectStoreException
254     {
255         QueueKey queueKey = (QueueKey) key;
256 
257         String filename = queueKey.id + FILE_EXTENSION;
258         String path = queueKey.queueName + File.separator + filename;
259 
260         try
261         {
262             return FileUtils.newFile(storeDirectory, path);
263         }
264         catch (MuleRuntimeException mre)
265         {
266             // FileUtils throws a MuleRuntimeException if something goes wrong when creating the
267             // path. To fully conform to the ObjectStore contract we cannot just let it bubble
268             // through but rather catch it and re-throw as ObjectStoreException
269             throw new ObjectStoreException(mre);
270         }
271     }
272 
273     @SuppressWarnings("unchecked")
274     protected T deserialize(File file) throws ObjectStoreException
275     {
276         try
277         {
278             FileInputStream in = new FileInputStream(file);
279             return (T)SerializationUtils.deserialize(in, muleContext);
280         }
281         catch (SerializationException se)
282         {
283             throw new ObjectStoreException(se);
284         }
285         catch (FileNotFoundException fnfe)
286         {
287             throw new ObjectStoreException(fnfe);
288         }
289     }
290 
291     @Override
292     protected T doRemove(Serializable key) throws ObjectStoreException
293     {
294         File storeFile = createStoreFile(key);
295         deleteStoreFile(storeFile);
296 
297         // we can safely return null here to avoid loading the message - the calling code
298         // discards the returned object anyway (see TransactionalQueueManager#doRemove)
299         return null;
300     }
301 
302     protected void deleteStoreFile(File file) throws ObjectStoreException
303     {
304         if (file.exists())
305         {
306             if (!file.delete())
307             {
308                 Message message =
309                     CoreMessages.createStaticMessage("Deleting " + file.getAbsolutePath() + " failed");
310                 throw new ObjectStoreException(message);
311             }
312         }
313         else
314         {
315             throw new ObjectDoesNotExistException();
316         }
317     }
318 
319     public void setMuleContext(MuleContext context)
320     {
321         muleContext = context;
322     }
323 }