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