View Javadoc

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