blob: d256de1426191bcbf26cc8f8992cd2dd024c9e72 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cocoon.components.store;
import org.apache.avalon.framework.CascadingRuntimeException;
import org.apache.avalon.framework.context.Context;
import org.apache.avalon.framework.context.ContextException;
import org.apache.avalon.framework.context.Contextualizable;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.thread.ThreadSafe;
import org.apache.avalon.framework.parameters.Parameterizable;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.parameters.ParameterException;
import org.apache.cocoon.Constants;
import org.apache.cocoon.util.IOUtils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.BitSet;
import java.util.Enumeration;
/**
* Stores objects on the filesystem: String objects as text files,
* all other objects are serialized.
*
* @deprecated Use the {@link org.apache.cocoon.components.store.impl.FilesystemStore}
*
* @author ?
* @author <a href="mailto:vgritsenko@apache.org">Vadim Gritsenko</a>
* @version CVS $Id$
*/
public final class FilesystemStore extends AbstractLogEnabled
implements Store, Contextualizable, Parameterizable, ThreadSafe {
protected File workDir;
protected File cacheDir;
/** The directory repository */
protected File directoryFile;
protected volatile String directoryPath;
/**
* Sets the repository's location
*/
public void setDirectory(final String directory)
throws IOException {
this.setDirectory(new File(directory));
}
public void contextualize(final Context context)
throws ContextException {
this.workDir = (File)context.get(Constants.CONTEXT_WORK_DIR);
this.cacheDir = (File)context.get(Constants.CONTEXT_CACHE_DIR);
}
public void parameterize(Parameters params)
throws ParameterException {
try {
if (params.getParameterAsBoolean("use-cache-directory", false)) {
if (this.getLogger().isDebugEnabled())
getLogger().debug("Using cache directory: " + cacheDir);
setDirectory(cacheDir);
} else if (params.getParameterAsBoolean("use-work-directory", false)) {
if (this.getLogger().isDebugEnabled())
getLogger().debug("Using work directory: " + workDir);
setDirectory(workDir);
} else if (params.getParameter("directory", null) != null) {
String dir = params.getParameter("directory");
dir = IOUtils.getContextFilePath(workDir.getPath(), dir);
if (this.getLogger().isDebugEnabled())
getLogger().debug("Using directory: " + dir);
setDirectory(new File(dir));
} else {
try {
// Legacy: use working directory by default
setDirectory(workDir);
} catch (IOException e) {
// Legacy: Always was ignored
}
}
} catch (IOException e) {
throw new ParameterException("Unable to set directory", e);
}
}
/**
* Sets the repository's location
*/
public void setDirectory(final File directory)
throws IOException {
this.directoryFile = directory;
/* Save directory path prefix */
this.directoryPath = IOUtils.getFullFilename(this.directoryFile);
this.directoryPath += File.separator;
/* Does directory exist? */
if (!this.directoryFile.exists()) {
/* Create it anew */
if (!this.directoryFile.mkdir()) {
throw new IOException(
"Error creating store directory '" + this.directoryPath + "': ");
}
}
/* Is given file actually a directory? */
if (!this.directoryFile.isDirectory()) {
throw new IOException("'" + this.directoryPath + "' is not a directory");
}
/* Is directory readable and writable? */
if (!(this.directoryFile.canRead() && this.directoryFile.canWrite())) {
throw new IOException(
"Directory '" + this.directoryPath + "' is not readable/writable"
);
}
}
/**
* Returns the repository's full pathname
*/
public String getDirectoryPath() {
return this.directoryPath;
}
/**
* Get the File object associated with the given unique key name.
*/
public synchronized Object get(final Object key) {
final File file = fileFromKey(key);
if (file != null && file.exists()) {
if (this.getLogger().isDebugEnabled()) {
getLogger().debug("Found file: " + key);
}
try {
return IOUtils.deserializeObject(file);
} catch (Exception any) {
getLogger().error("Error during deseralization.", any);
}
} else {
if (this.getLogger().isDebugEnabled()) {
getLogger().debug("NOT Found file: " + key);
}
}
return null;
}
/**
* Store the given object in a persistent state.
* 1) Null values generate empty directories.
* 2) String values are dumped to text files
* 3) Object values are serialized
*/
public synchronized void store(final Object key, final Object value)
throws IOException {
final File file = fileFromKey(key);
/* Create subdirectories as needed */
final File parent = file.getParentFile();
if (parent != null) {
parent.mkdirs();
}
/* Store object as file */
if (value == null) { /* Directory */
if (file.exists()) {
if (!file.delete()) { /* FAILURE */
getLogger().error("File cannot be deleted: " + file.toString());
return;
}
}
file.mkdir();
} else if (value instanceof String) {
/* Text file */
IOUtils.serializeString(file, (String) value);
} else {
/* Serialized Object */
IOUtils.serializeObject(file, value);
}
}
/**
* Holds the given object in a volatile state.
*/
public synchronized void hold(final Object key, final Object value)
throws IOException {
this.store(key, value);
final File file = this.fileFromKey(key);
if (file != null) {
file.deleteOnExit();
}
}
/**
* Remove the object associated to the given key.
*/
public synchronized void remove(final Object key) {
final File file = fileFromKey(key);
if (file != null) {
file.delete();
}
}
/**
* Indicates if the given key is associated to a contained object.
*/
public synchronized boolean containsKey(final Object key) {
final File file = fileFromKey(key);
if (file == null) {
return false;
}
return file.exists();
}
/**
* Returns the list of stored files as an Enumeration of Files
*/
public synchronized Enumeration keys() {
final FSEnumeration fsEnum = new FSEnumeration();
this.addKeys(fsEnum, this.directoryFile);
return fsEnum;
}
/**
* Returns count of the objects in the store, or -1 if could not be
* obtained.
*/
public synchronized int size() {
return countKeys(this.directoryFile);
}
protected void addKeys(FSEnumeration fsEnum, File directory) {
final int subStringBegin = this.directoryFile.getAbsolutePath().length() + 1;
final File[] files = directory.listFiles();
for (int i=0; i<files.length; i++) {
if (files[i].isDirectory()) {
this.addKeys(fsEnum, files[i]);
} else {
fsEnum.add(this.decode(files[i].getAbsolutePath().substring(subStringBegin)));
}
}
}
protected int countKeys(File directory) {
int count = 0;
final File[] files = directory.listFiles();
for (int i=0; i<files.length; i++) {
if (files[i].isDirectory()) {
count += this.countKeys(files[i]);
} else {
count ++;
}
}
return count;
}
final class FSEnumeration implements Enumeration {
private String[] array;
private int index;
private int length;
FSEnumeration() {
this.array = new String[16];
this.length = 0;
this.index = 0;
}
public void add(String key) {
if (this.length == array.length) {
String[] newarray = new String[this.length + 16];
System.arraycopy(this.array, 0, newarray, 0, this.array.length);
this.array = newarray;
}
this.array[this.length] = key;
this.length++;
}
public boolean hasMoreElements() {
return (this.index < this.length);
}
public Object nextElement() {
if (this.hasMoreElements()) {
this.index++;
return this.array[index-1];
}
return null;
}
}
/* Utility Methods*/
protected File fileFromKey(final Object key) {
return IOUtils.createFile(this.directoryFile, this.encode(key.toString()));
}
public String getString(final Object key)
throws IOException {
final File file = this.fileFromKey(key);
if (file != null) {
return IOUtils.deserializeString(file);
}
return null;
}
public synchronized void free() {}
public synchronized Object getObject(final Object key)
throws IOException, ClassNotFoundException
{
final File file = this.fileFromKey(key);
if (file != null) {
return IOUtils.deserializeObject(file);
}
return null;
}
/**
* Inverse of encode exept it do not use path.
* So decode(encode(s) - m_path) = s.
* In other words it returns a String that can be used as key to retive
* the record contained in the 'filename' file.
*/
protected String decode( String filename )
{
try {
return java.net.URLDecoder.decode( filename );
} catch (Exception local) {
throw new CascadingRuntimeException("Exception in decode", local);
}
}
/** A BitSet defining the characters which don't need encoding */
static BitSet charactersDontNeedingEncoding;
static final int characterCaseDiff = ('a' - 'A');
/** Initialize the BitSet */
static
{
charactersDontNeedingEncoding = new BitSet(256);
int i;
for (i = 'a'; i <= 'z'; i++)
{
charactersDontNeedingEncoding.set(i);
}
for (i = 'A'; i <= 'Z'; i++)
{
charactersDontNeedingEncoding.set(i);
}
for (i = '0'; i <= '9'; i++)
{
charactersDontNeedingEncoding.set(i);
}
charactersDontNeedingEncoding.set('-');
charactersDontNeedingEncoding.set('_');
charactersDontNeedingEncoding.set('(');
charactersDontNeedingEncoding.set(')');
}
/**
* Returns a String that uniquely identifies the object.
* <b>Note:</b> since this method uses the Object.toString()
* method, it's up to the caller to make sure that this method
* doesn't change between different JVM executions (like
* it may normally happen). For this reason, it's highly recommended
* (even if not mandated) that Strings be used as keys.
*/
public String encode(String s) {
final StringBuffer out = new StringBuffer( s.length() );
final ByteArrayOutputStream buf = new ByteArrayOutputStream( 32 );
final OutputStreamWriter writer = new OutputStreamWriter( buf );
for (int i = 0; i < s.length(); i++)
{
int c = s.charAt(i);
if (charactersDontNeedingEncoding.get(c))
{
out.append((char)c);
}
else
{
try
{
writer.write(c);
writer.flush();
}
catch(IOException e)
{
buf.reset();
continue;
}
byte[] ba = buf.toByteArray();
for (int j = 0; j < ba.length; j++)
{
out.append('%');
char ch = Character.forDigit((ba[j] >> 4) & 0xF, 16);
// converting to use uppercase letter as part of
// the hex value if ch is a letter.
if (Character.isLetter(ch))
{
ch -= characterCaseDiff;
}
out.append(ch);
ch = Character.forDigit(ba[j] & 0xF, 16);
if (Character.isLetter(ch))
{
ch -= characterCaseDiff;
}
out.append(ch);
}
buf.reset();
}
}
return out.toString();
}
}