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
109
110 final File directory = new File( m_path );
111 directory.mkdirs();
112
113 getLogger().info( getClass().getName() + " opened in " + m_path );
114
115
116
117
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
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
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 }