001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.components.store;
018:
019: import org.apache.avalon.framework.CascadingRuntimeException;
020: import org.apache.avalon.framework.context.Context;
021: import org.apache.avalon.framework.context.ContextException;
022: import org.apache.avalon.framework.context.Contextualizable;
023: import org.apache.avalon.framework.logger.AbstractLogEnabled;
024: import org.apache.avalon.framework.thread.ThreadSafe;
025: import org.apache.avalon.framework.parameters.Parameterizable;
026: import org.apache.avalon.framework.parameters.Parameters;
027: import org.apache.avalon.framework.parameters.ParameterException;
028: import org.apache.cocoon.Constants;
029: import org.apache.cocoon.util.IOUtils;
030: import java.io.ByteArrayOutputStream;
031: import java.io.File;
032: import java.io.IOException;
033: import java.io.OutputStreamWriter;
034: import java.util.BitSet;
035: import java.util.Enumeration;
036:
037: /**
038: * Stores objects on the filesystem: String objects as text files,
039: * all other objects are serialized.
040: *
041: * @deprecated Use the {@link org.apache.cocoon.components.store.impl.FilesystemStore}
042: *
043: * @author ?
044: * @author <a href="mailto:vgritsenko@apache.org">Vadim Gritsenko</a>
045: * @version CVS $Id: FilesystemStore.java 433543 2006-08-22 06:22:54Z crossley $
046: */
047: public final class FilesystemStore extends AbstractLogEnabled implements
048: Store, Contextualizable, Parameterizable, ThreadSafe {
049:
050: protected File workDir;
051: protected File cacheDir;
052:
053: /** The directory repository */
054: protected File directoryFile;
055: protected volatile String directoryPath;
056:
057: /**
058: * Sets the repository's location
059: */
060: public void setDirectory(final String directory) throws IOException {
061: this .setDirectory(new File(directory));
062: }
063:
064: public void contextualize(final Context context)
065: throws ContextException {
066: this .workDir = (File) context.get(Constants.CONTEXT_WORK_DIR);
067: this .cacheDir = (File) context.get(Constants.CONTEXT_CACHE_DIR);
068: }
069:
070: public void parameterize(Parameters params)
071: throws ParameterException {
072: try {
073: if (params.getParameterAsBoolean("use-cache-directory",
074: false)) {
075: if (this .getLogger().isDebugEnabled())
076: getLogger().debug(
077: "Using cache directory: " + cacheDir);
078: setDirectory(cacheDir);
079: } else if (params.getParameterAsBoolean(
080: "use-work-directory", false)) {
081: if (this .getLogger().isDebugEnabled())
082: getLogger().debug(
083: "Using work directory: " + workDir);
084: setDirectory(workDir);
085: } else if (params.getParameter("directory", null) != null) {
086: String dir = params.getParameter("directory");
087: dir = IOUtils
088: .getContextFilePath(workDir.getPath(), dir);
089: if (this .getLogger().isDebugEnabled())
090: getLogger().debug("Using directory: " + dir);
091: setDirectory(new File(dir));
092: } else {
093: try {
094: // Legacy: use working directory by default
095: setDirectory(workDir);
096: } catch (IOException e) {
097: // Legacy: Always was ignored
098: }
099: }
100: } catch (IOException e) {
101: throw new ParameterException("Unable to set directory", e);
102: }
103: }
104:
105: /**
106: * Sets the repository's location
107: */
108: public void setDirectory(final File directory) throws IOException {
109: this .directoryFile = directory;
110:
111: /* Save directory path prefix */
112: this .directoryPath = IOUtils
113: .getFullFilename(this .directoryFile);
114: this .directoryPath += File.separator;
115:
116: /* Does directory exist? */
117: if (!this .directoryFile.exists()) {
118: /* Create it anew */
119: if (!this .directoryFile.mkdir()) {
120: throw new IOException(
121: "Error creating store directory '"
122: + this .directoryPath + "': ");
123: }
124: }
125:
126: /* Is given file actually a directory? */
127: if (!this .directoryFile.isDirectory()) {
128: throw new IOException("'" + this .directoryPath
129: + "' is not a directory");
130: }
131:
132: /* Is directory readable and writable? */
133: if (!(this .directoryFile.canRead() && this .directoryFile
134: .canWrite())) {
135: throw new IOException("Directory '" + this .directoryPath
136: + "' is not readable/writable");
137: }
138: }
139:
140: /**
141: * Returns the repository's full pathname
142: */
143: public String getDirectoryPath() {
144: return this .directoryPath;
145: }
146:
147: /**
148: * Get the File object associated with the given unique key name.
149: */
150: public synchronized Object get(final Object key) {
151: final File file = fileFromKey(key);
152:
153: if (file != null && file.exists()) {
154: if (this .getLogger().isDebugEnabled()) {
155: getLogger().debug("Found file: " + key);
156: }
157: try {
158: return IOUtils.deserializeObject(file);
159: } catch (Exception any) {
160: getLogger().error("Error during deseralization.", any);
161: }
162: } else {
163: if (this .getLogger().isDebugEnabled()) {
164: getLogger().debug("NOT Found file: " + key);
165: }
166: }
167:
168: return null;
169: }
170:
171: /**
172: * Store the given object in a persistent state.
173: * 1) Null values generate empty directories.
174: * 2) String values are dumped to text files
175: * 3) Object values are serialized
176: */
177: public synchronized void store(final Object key, final Object value)
178: throws IOException {
179: final File file = fileFromKey(key);
180:
181: /* Create subdirectories as needed */
182: final File parent = file.getParentFile();
183: if (parent != null) {
184: parent.mkdirs();
185: }
186:
187: /* Store object as file */
188: if (value == null) { /* Directory */
189: if (file.exists()) {
190: if (!file.delete()) { /* FAILURE */
191: getLogger().error(
192: "File cannot be deleted: "
193: + file.toString());
194: return;
195: }
196: }
197:
198: file.mkdir();
199: } else if (value instanceof String) {
200: /* Text file */
201: IOUtils.serializeString(file, (String) value);
202: } else {
203: /* Serialized Object */
204: IOUtils.serializeObject(file, value);
205: }
206: }
207:
208: /**
209: * Holds the given object in a volatile state.
210: */
211: public synchronized void hold(final Object key, final Object value)
212: throws IOException {
213: this .store(key, value);
214: final File file = this .fileFromKey(key);
215: if (file != null) {
216: file.deleteOnExit();
217: }
218: }
219:
220: /**
221: * Remove the object associated to the given key.
222: */
223: public synchronized void remove(final Object key) {
224: final File file = fileFromKey(key);
225: if (file != null) {
226: file.delete();
227: }
228: }
229:
230: /**
231: * Indicates if the given key is associated to a contained object.
232: */
233: public synchronized boolean containsKey(final Object key) {
234: final File file = fileFromKey(key);
235: if (file == null) {
236: return false;
237: }
238: return file.exists();
239: }
240:
241: /**
242: * Returns the list of stored files as an Enumeration of Files
243: */
244: public synchronized Enumeration keys() {
245: final FSEnumeration fsEnum = new FSEnumeration();
246: this .addKeys(fsEnum, this .directoryFile);
247: return fsEnum;
248: }
249:
250: /**
251: * Returns count of the objects in the store, or -1 if could not be
252: * obtained.
253: */
254: public synchronized int size() {
255: return countKeys(this .directoryFile);
256: }
257:
258: protected void addKeys(FSEnumeration fsEnum, File directory) {
259: final int subStringBegin = this .directoryFile.getAbsolutePath()
260: .length() + 1;
261: final File[] files = directory.listFiles();
262: for (int i = 0; i < files.length; i++) {
263: if (files[i].isDirectory()) {
264: this .addKeys(fsEnum, files[i]);
265: } else {
266: fsEnum.add(this .decode(files[i].getAbsolutePath()
267: .substring(subStringBegin)));
268: }
269: }
270: }
271:
272: protected int countKeys(File directory) {
273: int count = 0;
274: final File[] files = directory.listFiles();
275: for (int i = 0; i < files.length; i++) {
276: if (files[i].isDirectory()) {
277: count += this .countKeys(files[i]);
278: } else {
279: count++;
280: }
281: }
282: return count;
283: }
284:
285: final class FSEnumeration implements Enumeration {
286: private String[] array;
287: private int index;
288: private int length;
289:
290: FSEnumeration() {
291: this .array = new String[16];
292: this .length = 0;
293: this .index = 0;
294: }
295:
296: public void add(String key) {
297: if (this .length == array.length) {
298: String[] newarray = new String[this .length + 16];
299: System.arraycopy(this .array, 0, newarray, 0,
300: this .array.length);
301: this .array = newarray;
302: }
303: this .array[this .length] = key;
304: this .length++;
305: }
306:
307: public boolean hasMoreElements() {
308: return (this .index < this .length);
309: }
310:
311: public Object nextElement() {
312: if (this .hasMoreElements()) {
313: this .index++;
314: return this .array[index - 1];
315: }
316: return null;
317: }
318: }
319:
320: /* Utility Methods*/
321: protected File fileFromKey(final Object key) {
322: return IOUtils.createFile(this .directoryFile, this .encode(key
323: .toString()));
324: }
325:
326: public String getString(final Object key) throws IOException {
327: final File file = this .fileFromKey(key);
328: if (file != null) {
329: return IOUtils.deserializeString(file);
330: }
331:
332: return null;
333: }
334:
335: public synchronized void free() {
336: }
337:
338: public synchronized Object getObject(final Object key)
339: throws IOException, ClassNotFoundException {
340: final File file = this .fileFromKey(key);
341: if (file != null) {
342: return IOUtils.deserializeObject(file);
343: }
344:
345: return null;
346: }
347:
348: /**
349: * Inverse of encode exept it do not use path.
350: * So decode(encode(s) - m_path) = s.
351: * In other words it returns a String that can be used as key to retive
352: * the record contained in the 'filename' file.
353: */
354: protected String decode(String filename) {
355: try {
356: return java.net.URLDecoder.decode(filename);
357: } catch (Exception local) {
358: throw new CascadingRuntimeException("Exception in decode",
359: local);
360: }
361: }
362:
363: /** A BitSet defining the characters which don't need encoding */
364: static BitSet charactersDontNeedingEncoding;
365: static final int characterCaseDiff = ('a' - 'A');
366:
367: /** Initialize the BitSet */
368: static {
369: charactersDontNeedingEncoding = new BitSet(256);
370: int i;
371: for (i = 'a'; i <= 'z'; i++) {
372: charactersDontNeedingEncoding.set(i);
373: }
374: for (i = 'A'; i <= 'Z'; i++) {
375: charactersDontNeedingEncoding.set(i);
376: }
377: for (i = '0'; i <= '9'; i++) {
378: charactersDontNeedingEncoding.set(i);
379: }
380: charactersDontNeedingEncoding.set('-');
381: charactersDontNeedingEncoding.set('_');
382: charactersDontNeedingEncoding.set('(');
383: charactersDontNeedingEncoding.set(')');
384: }
385:
386: /**
387: * Returns a String that uniquely identifies the object.
388: * <b>Note:</b> since this method uses the Object.toString()
389: * method, it's up to the caller to make sure that this method
390: * doesn't change between different JVM executions (like
391: * it may normally happen). For this reason, it's highly recommended
392: * (even if not mandated) that Strings be used as keys.
393: */
394: public String encode(String s) {
395: final StringBuffer out = new StringBuffer(s.length());
396: final ByteArrayOutputStream buf = new ByteArrayOutputStream(32);
397: final OutputStreamWriter writer = new OutputStreamWriter(buf);
398: for (int i = 0; i < s.length(); i++) {
399: int c = s.charAt(i);
400: if (charactersDontNeedingEncoding.get(c)) {
401: out.append((char) c);
402: } else {
403: try {
404: writer.write(c);
405: writer.flush();
406: } catch (IOException e) {
407: buf.reset();
408: continue;
409: }
410: byte[] ba = buf.toByteArray();
411: for (int j = 0; j < ba.length; j++) {
412: out.append('%');
413: char ch = Character
414: .forDigit((ba[j] >> 4) & 0xF, 16);
415: // converting to use uppercase letter as part of
416: // the hex value if ch is a letter.
417: if (Character.isLetter(ch)) {
418: ch -= characterCaseDiff;
419: }
420: out.append(ch);
421: ch = Character.forDigit(ba[j] & 0xF, 16);
422: if (Character.isLetter(ch)) {
423: ch -= characterCaseDiff;
424: }
425: out.append(ch);
426: }
427: buf.reset();
428: }
429: }
430:
431: return out.toString();
432: }
433: }
|