View Javadoc

1   /************************************************************************
2    * Copyright (c) 2000-2006 The Apache Software Foundation.             *
3    * All rights reserved.                                                *
4    * ------------------------------------------------------------------- *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you *
6    * may not use this file except in compliance with the License. You    *
7    * may obtain a copy of the License at:                                *
8    *                                                                     *
9    *     http://www.apache.org/licenses/LICENSE-2.0                      *
10   *                                                                     *
11   * Unless required by applicable law or agreed to in writing, software *
12   * distributed under the License is distributed on an "AS IS" BASIS,   *
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or     *
14   * implied.  See the License for the specific language governing       *
15   * permissions and limitations under the License.                      *
16   ***********************************************************************/
17  
18  package org.apache.james.mailrepository.filepair;
19  
20  import java.io.File;
21  import java.io.FileInputStream;
22  import java.io.FileOutputStream;
23  import java.io.FilenameFilter;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.OutputStream;
27  import java.util.ArrayList;
28  import java.util.Iterator;
29  import org.apache.avalon.cornerstone.services.store.Repository;
30  import org.apache.james.util.io.ExtensionFileFilter;
31  import org.apache.avalon.framework.activity.Initializable;
32  import org.apache.avalon.framework.configuration.Configurable;
33  import org.apache.avalon.framework.configuration.Configuration;
34  import org.apache.avalon.framework.configuration.ConfigurationException;
35  import org.apache.avalon.framework.context.Context;
36  import org.apache.avalon.framework.context.Contextualizable;
37  import org.apache.avalon.framework.context.ContextException;
38  import org.apache.avalon.framework.logger.AbstractLogEnabled;
39  import org.apache.avalon.framework.service.ServiceException;
40  import org.apache.avalon.framework.service.ServiceManager;
41  import org.apache.avalon.framework.service.Serviceable;
42  
43  /***
44   * This an abstract class implementing functionality for creating a file-store.
45   *
46   */
47  public abstract class AbstractFileRepository
48      extends AbstractLogEnabled
49      implements Repository, Contextualizable, Serviceable, Configurable, Initializable
50  {
51      protected static final boolean DEBUG = false;
52  
53      protected static final String HANDLED_URL = "file://";
54      protected static final int BYTE_MASK = 0x0f;
55      protected static final char[] HEX_DIGITS = new char[]
56      {
57          '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
58      };
59  
60      protected String m_path;
61      protected String m_destination;
62      protected String m_extension;
63      protected String m_name;
64      protected FilenameFilter m_filter;
65      protected File m_baseDirectory;
66  
67      protected ServiceManager m_serviceManager;
68  
69      protected abstract String getExtensionDecorator();
70  
71      public void contextualize( final Context context ) throws ContextException
72      {
73          try
74          {
75              m_baseDirectory = (File) context.get( "urn:avalon:home" );
76          }
77          catch( ContextException ce )
78          {
79              m_baseDirectory = (File) context.get( "app.home" );
80          }
81      }
82  
83      public void service( final ServiceManager serviceManager )
84          throws ServiceException
85      {
86          m_serviceManager = serviceManager;
87      }
88  
89      public void configure( final Configuration configuration )
90          throws ConfigurationException
91      {
92          if( null == m_destination )
93          {
94              final String destination = configuration.getAttribute( "destinationURL" );
95              setDestination( destination );
96          }
97      }
98  
99      public void initialize()
100         throws Exception
101     {
102         getLogger().info( "Init " + getClass().getName() + " Store" );
103 
104         m_name = RepositoryManager.getName();
105         String m_postfix = getExtensionDecorator();
106         m_extension = "." + m_name + m_postfix;
107         m_filter = new ExtensionFileFilter(m_extension);
108         //m_filter = new NumberedRepositoryFileFilter(getExtensionDecorator());
109 
110         final File directory = new File( m_path );
111         directory.mkdirs();
112 
113         getLogger().info( getClass().getName() + " opened in " + m_path );
114 
115         //We will look for all numbered repository files in this
116         //  directory and rename them to non-numbered repositories,
117         //  logging all the way.
118 
119         FilenameFilter num_filter = new NumberedRepositoryFileFilter(getExtensionDecorator());
120         final String[] names = directory.list( num_filter );
121 
122         try {
123             for( int i = 0; i < names.length; i++ ) {
124                 String origFilename = names[i];
125 
126                 //This needs to handle (skip over) the possible repository numbers
127                 int pos = origFilename.length() - m_postfix.length();
128                 while (pos >= 1 && Character.isDigit(origFilename.charAt(pos - 1))) {
129                     pos--;
130                 }
131                 pos -= ".".length() + m_name.length();
132                 String newFilename = origFilename.substring(0, pos) + m_extension;
133 
134                 File origFile = new File(directory, origFilename);
135                 File newFile = new File(directory, newFilename);
136 
137                 if (origFile.renameTo(newFile)) {
138                     getLogger().info("Renamed " + origFile + " to " + newFile);
139                 } else {
140                     getLogger().info("Unable to rename " + origFile + " to " + newFile);
141                 }
142             }
143         } catch (Exception e) {
144             e.printStackTrace();
145             throw e;
146         }
147 
148     }
149 
150     protected void setDestination( final String destination )
151         throws ConfigurationException
152     {
153         if( !destination.startsWith( HANDLED_URL ) )
154         {
155             throw new ConfigurationException( "cannot handle destination " + destination );
156         }
157 
158         m_path = destination.substring( HANDLED_URL.length() );
159 
160         File directory;
161 
162         // Check for absolute path
163         if( m_path.startsWith( "/" ) )
164         {
165             directory = new File( m_path );
166         }
167         else
168         {
169             directory = new File( m_baseDirectory, m_path );
170         }
171 
172         try
173         {
174             directory = directory.getCanonicalFile();
175         }
176         catch( final IOException ioe )
177         {
178             throw new ConfigurationException( "Unable to form canonical representation of " +
179                                               directory );
180         }
181 
182         m_path = directory.toString();
183 
184         m_destination = destination;
185     }
186 
187     protected AbstractFileRepository createChildRepository()
188         throws Exception
189     {
190         return (AbstractFileRepository) getClass().newInstance();
191     }
192 
193     public Repository getChildRepository( final String childName )
194     {
195         AbstractFileRepository child = null;
196 
197         try
198         {
199             child = createChildRepository();
200         }
201         catch( final Exception e )
202         {
203             throw new RuntimeException( "Cannot create child repository " +
204                                         childName + " : " + e );
205         }
206 
207         try
208         {
209             child.service( m_serviceManager );
210         }
211         catch( final ServiceException cme )
212         {
213             throw new RuntimeException( "Cannot service child " +
214                                         "repository " + childName +
215                                         " : " + cme );
216         }
217 
218         try
219         {
220             child.setDestination( m_destination + File.pathSeparatorChar +
221                                   childName + File.pathSeparator );
222         }
223         catch( final ConfigurationException ce )
224         {
225             throw new RuntimeException( "Cannot set destination for child child " +
226                                         "repository " + childName +
227                                         " : " + ce );
228         }
229 
230         try
231         {
232             child.initialize();
233         }
234         catch( final Exception e )
235         {
236             throw new RuntimeException( "Cannot initialize child " +
237                                         "repository " + childName +
238                                         " : " + e );
239         }
240 
241         if( DEBUG )
242         {
243             getLogger().debug( "Child repository of " + m_name + " created in " +
244                                m_destination + File.pathSeparatorChar +
245                                childName + File.pathSeparator );
246         }
247 
248         return child;
249     }
250 
251     protected File getFile( final String key )
252         throws IOException
253     {
254         return new File( encode( key ) );
255     }
256 
257     protected InputStream getInputStream( final String key )
258         throws IOException
259     {
260         return new FileInputStream( encode( key ) );
261     }
262 
263     protected OutputStream getOutputStream( final String key )
264         throws IOException
265     {
266         return new FileOutputStream( getFile( key ) );
267     }
268 
269     /***
270      * Remove the object associated to the given key.
271      */
272     public synchronized void remove( final String key )
273     {
274         try
275         {
276             final File file = getFile( key );
277             file.delete();
278             if( DEBUG ) getLogger().debug( "removed key " + key );
279         }
280         catch( final Exception e )
281         {
282             throw new RuntimeException( "Exception caught while removing" +
283                                         " an object: " + e );
284         }
285     }
286 
287     /***
288      * Indicates if the given key is associated to a contained object.
289      */
290     public synchronized boolean containsKey( final String key )
291     {
292         try
293         {
294             final File file = getFile( key );
295             if( DEBUG ) getLogger().debug( "checking key " + key );
296             return file.exists();
297         }
298         catch( final Exception e )
299         {
300             throw new RuntimeException( "Exception caught while searching " +
301                                         "an object: " + e );
302         }
303     }
304 
305     /***
306      * Returns the list of used keys.
307      */
308     public Iterator list()
309     {
310         final File storeDir = new File( m_path );
311         final String[] names = storeDir.list( m_filter );
312         final ArrayList list = new ArrayList();
313 
314         for( int i = 0; i < names.length; i++ )
315         {
316             String decoded = decode(names[i]);
317             list.add(decoded);
318         }
319 
320         return list.iterator();
321     }
322 
323     /***
324      * Returns a String that uniquely identifies the object.
325      * <b>Note:</b> since this method uses the Object.toString()
326      * method, it's up to the caller to make sure that this method
327      * doesn't change between different JVM executions (like
328      * it may normally happen). For this reason, it's highly recommended
329      * (even if not mandated) that Strings be used as keys.
330      */
331     protected String encode( final String key )
332     {
333         final byte[] bytes = key.getBytes();
334         final char[] buffer = new char[ bytes.length << 1 ];
335 
336         for( int i = 0, j = 0; i < bytes.length; i++ )
337         {
338             final int k = bytes[ i ];
339             buffer[ j++ ] = HEX_DIGITS[ ( k >>> 4 ) & BYTE_MASK ];
340             buffer[ j++ ] = HEX_DIGITS[ k & BYTE_MASK ];
341         }
342 
343         StringBuffer result = new StringBuffer();
344         result.append( m_path );
345         result.append( File.separator );
346         result.append( buffer );
347         result.append( m_extension );
348         return result.toString();
349     }
350 
351     /***
352      * Inverse of encode exept it do not use path.
353      * So decode(encode(s) - m_path) = s.
354      * In other words it returns a String that can be used as key to retive
355      * the record contained in the 'filename' file.
356      */
357     protected String decode( String filename )
358     {
359         filename = filename.substring( 0, filename.length() - m_extension.length() );
360         final int size = filename.length();
361         final byte[] bytes = new byte[ size >>> 1 ];
362 
363         for( int i = 0, j = 0; i < size; j++ )
364         {
365             bytes[ j ] = Byte.parseByte( filename.substring( i, i + 2 ), 16 );
366             i +=2;
367         }
368 
369         return new String( bytes );
370     }
371 }