001: /****************************************************************
002: * Licensed to the Apache Software Foundation (ASF) under one *
003: * or more contributor license agreements. See the NOTICE file *
004: * distributed with this work for additional information *
005: * regarding copyright ownership. The ASF licenses this file *
006: * to you under the Apache License, Version 2.0 (the *
007: * "License"); you may not use this file except in compliance *
008: * with the License. You may obtain a copy of the License at *
009: * *
010: * http://www.apache.org/licenses/LICENSE-2.0 *
011: * *
012: * Unless required by applicable law or agreed to in writing, *
013: * software distributed under the License is distributed on an *
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015: * KIND, either express or implied. See the License for the *
016: * specific language governing permissions and limitations *
017: * under the License. *
018: ****************************************************************/package org.apache.james.mailrepository.filepair;
019:
020: import java.io.File;
021: import java.io.FileInputStream;
022: import java.io.FileOutputStream;
023: import java.io.FilenameFilter;
024: import java.io.IOException;
025: import java.io.InputStream;
026: import java.io.OutputStream;
027: import java.util.ArrayList;
028: import java.util.Iterator;
029: import org.apache.avalon.cornerstone.services.store.Repository;
030: import org.apache.james.util.io.ExtensionFileFilter;
031: import org.apache.avalon.framework.activity.Initializable;
032: import org.apache.avalon.framework.configuration.Configurable;
033: import org.apache.avalon.framework.configuration.Configuration;
034: import org.apache.avalon.framework.configuration.ConfigurationException;
035: import org.apache.avalon.framework.context.Context;
036: import org.apache.avalon.framework.context.Contextualizable;
037: import org.apache.avalon.framework.context.ContextException;
038: import org.apache.avalon.framework.logger.AbstractLogEnabled;
039: import org.apache.avalon.framework.service.ServiceException;
040: import org.apache.avalon.framework.service.ServiceManager;
041: import org.apache.avalon.framework.service.Serviceable;
042:
043: /**
044: * This an abstract class implementing functionality for creating a file-store.
045: *
046: */
047: public abstract class AbstractFileRepository extends AbstractLogEnabled
048: implements Repository, Contextualizable, Serviceable,
049: Configurable, Initializable {
050: protected static final boolean DEBUG = false;
051:
052: protected static final String HANDLED_URL = "file://";
053: protected static final int BYTE_MASK = 0x0f;
054: protected static final char[] HEX_DIGITS = new char[] { '0', '1',
055: '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
056: 'E', 'F' };
057:
058: protected String m_path;
059: protected String m_destination;
060: protected String m_extension;
061: protected String m_name;
062: protected FilenameFilter m_filter;
063: protected File m_baseDirectory;
064:
065: protected ServiceManager m_serviceManager;
066:
067: protected abstract String getExtensionDecorator();
068:
069: public void contextualize(final Context context)
070: throws ContextException {
071: try {
072: m_baseDirectory = (File) context.get("urn:avalon:home");
073: } catch (ContextException ce) {
074: m_baseDirectory = (File) context.get("app.home");
075: }
076: }
077:
078: public void service(final ServiceManager serviceManager)
079: throws ServiceException {
080: m_serviceManager = serviceManager;
081: }
082:
083: public void configure(final Configuration configuration)
084: throws ConfigurationException {
085: if (null == m_destination) {
086: final String destination = configuration
087: .getAttribute("destinationURL");
088: setDestination(destination);
089: }
090: }
091:
092: public void initialize() throws Exception {
093: getLogger().info("Init " + getClass().getName() + " Store");
094:
095: m_name = RepositoryManager.getName();
096: String m_postfix = getExtensionDecorator();
097: m_extension = "." + m_name + m_postfix;
098: m_filter = new ExtensionFileFilter(m_extension);
099: //m_filter = new NumberedRepositoryFileFilter(getExtensionDecorator());
100:
101: final File directory = new File(m_path);
102: directory.mkdirs();
103:
104: getLogger().info(getClass().getName() + " opened in " + m_path);
105:
106: //We will look for all numbered repository files in this
107: // directory and rename them to non-numbered repositories,
108: // logging all the way.
109:
110: FilenameFilter num_filter = new NumberedRepositoryFileFilter(
111: getExtensionDecorator());
112: final String[] names = directory.list(num_filter);
113:
114: try {
115: for (int i = 0; i < names.length; i++) {
116: String origFilename = names[i];
117:
118: //This needs to handle (skip over) the possible repository numbers
119: int pos = origFilename.length() - m_postfix.length();
120: while (pos >= 1
121: && Character.isDigit(origFilename
122: .charAt(pos - 1))) {
123: pos--;
124: }
125: pos -= ".".length() + m_name.length();
126: String newFilename = origFilename.substring(0, pos)
127: + m_extension;
128:
129: File origFile = new File(directory, origFilename);
130: File newFile = new File(directory, newFilename);
131:
132: if (origFile.renameTo(newFile)) {
133: getLogger().info(
134: "Renamed " + origFile + " to " + newFile);
135: } else {
136: getLogger().info(
137: "Unable to rename " + origFile + " to "
138: + newFile);
139: }
140: }
141: } catch (Exception e) {
142: e.printStackTrace();
143: throw e;
144: }
145:
146: }
147:
148: protected void setDestination(final String destination)
149: throws ConfigurationException {
150: if (!destination.startsWith(HANDLED_URL)) {
151: throw new ConfigurationException(
152: "cannot handle destination " + destination);
153: }
154:
155: m_path = destination.substring(HANDLED_URL.length());
156:
157: File directory;
158:
159: // Check for absolute path
160: if (m_path.startsWith("/")) {
161: directory = new File(m_path);
162: } else {
163: directory = new File(m_baseDirectory, m_path);
164: }
165:
166: try {
167: directory = directory.getCanonicalFile();
168: } catch (final IOException ioe) {
169: throw new ConfigurationException(
170: "Unable to form canonical representation of "
171: + directory);
172: }
173:
174: m_path = directory.toString();
175:
176: m_destination = destination;
177: }
178:
179: protected AbstractFileRepository createChildRepository()
180: throws Exception {
181: return (AbstractFileRepository) getClass().newInstance();
182: }
183:
184: public Repository getChildRepository(final String childName) {
185: AbstractFileRepository child = null;
186:
187: try {
188: child = createChildRepository();
189: } catch (final Exception e) {
190: throw new RuntimeException(
191: "Cannot create child repository " + childName
192: + " : " + e);
193: }
194:
195: try {
196: child.service(m_serviceManager);
197: } catch (final ServiceException cme) {
198: throw new RuntimeException("Cannot service child "
199: + "repository " + childName + " : " + cme);
200: }
201:
202: try {
203: child.setDestination(m_destination + File.pathSeparatorChar
204: + childName + File.pathSeparator);
205: } catch (final ConfigurationException ce) {
206: throw new RuntimeException(
207: "Cannot set destination for child child "
208: + "repository " + childName + " : " + ce);
209: }
210:
211: try {
212: child.initialize();
213: } catch (final Exception e) {
214: throw new RuntimeException("Cannot initialize child "
215: + "repository " + childName + " : " + e);
216: }
217:
218: if (DEBUG) {
219: getLogger().debug(
220: "Child repository of " + m_name + " created in "
221: + m_destination + File.pathSeparatorChar
222: + childName + File.pathSeparator);
223: }
224:
225: return child;
226: }
227:
228: protected File getFile(final String key) throws IOException {
229: return new File(encode(key));
230: }
231:
232: protected InputStream getInputStream(final String key)
233: throws IOException {
234: return new FileInputStream(encode(key));
235: }
236:
237: protected OutputStream getOutputStream(final String key)
238: throws IOException {
239: return new FileOutputStream(getFile(key));
240: }
241:
242: /**
243: * Remove the object associated to the given key.
244: */
245: public synchronized void remove(final String key) {
246: try {
247: final File file = getFile(key);
248: file.delete();
249: if (DEBUG)
250: getLogger().debug("removed key " + key);
251: } catch (final Exception e) {
252: throw new RuntimeException(
253: "Exception caught while removing" + " an object: "
254: + e);
255: }
256: }
257:
258: /**
259: * Indicates if the given key is associated to a contained object.
260: */
261: public synchronized boolean containsKey(final String key) {
262: try {
263: final File file = getFile(key);
264: if (DEBUG)
265: getLogger().debug("checking key " + key);
266: return file.exists();
267: } catch (final Exception e) {
268: throw new RuntimeException(
269: "Exception caught while searching " + "an object: "
270: + e);
271: }
272: }
273:
274: /**
275: * Returns the list of used keys.
276: */
277: public Iterator list() {
278: final File storeDir = new File(m_path);
279: final String[] names = storeDir.list(m_filter);
280: final ArrayList list = new ArrayList();
281:
282: for (int i = 0; i < names.length; i++) {
283: String decoded = decode(names[i]);
284: list.add(decoded);
285: }
286:
287: return list.iterator();
288: }
289:
290: /**
291: * Returns a String that uniquely identifies the object.
292: * <b>Note:</b> since this method uses the Object.toString()
293: * method, it's up to the caller to make sure that this method
294: * doesn't change between different JVM executions (like
295: * it may normally happen). For this reason, it's highly recommended
296: * (even if not mandated) that Strings be used as keys.
297: */
298: protected String encode(final String key) {
299: final byte[] bytes = key.getBytes();
300: final char[] buffer = new char[bytes.length << 1];
301:
302: for (int i = 0, j = 0; i < bytes.length; i++) {
303: final int k = bytes[i];
304: buffer[j++] = HEX_DIGITS[(k >>> 4) & BYTE_MASK];
305: buffer[j++] = HEX_DIGITS[k & BYTE_MASK];
306: }
307:
308: StringBuffer result = new StringBuffer();
309: result.append(m_path);
310: result.append(File.separator);
311: result.append(buffer);
312: result.append(m_extension);
313: return result.toString();
314: }
315:
316: /**
317: * Inverse of encode exept it do not use path.
318: * So decode(encode(s) - m_path) = s.
319: * In other words it returns a String that can be used as key to retive
320: * the record contained in the 'filename' file.
321: */
322: protected String decode(String filename) {
323: filename = filename.substring(0, filename.length()
324: - m_extension.length());
325: final int size = filename.length();
326: final byte[] bytes = new byte[size >>> 1];
327:
328: for (int i = 0, j = 0; i < size; j++) {
329: bytes[j] = Byte.parseByte(filename.substring(i, i + 2), 16);
330: i += 2;
331: }
332:
333: return new String(bytes);
334: }
335: }
|