/*=============================================================================*
* Copyright 2006 The Apache Software Foundation
*
* Licensed 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.muse.core;
import java.io.File;
import java.io.FileFilter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeSet;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.apache.muse.util.messages.Messages;
import org.apache.muse.util.messages.MessagesFactory;
import org.apache.muse.util.xml.XmlUtils;
import org.apache.muse.ws.addressing.EndpointReference;
import org.apache.muse.ws.addressing.soap.SoapFault;
/**
*
* AbstractFilePersistence is an abstract component that provides generic
* resource-state-to-file utilities without specifying the format of the XML
* that goes into the files. It can be used by resources or capabilities that
* wish to save state to disk and reload that state the next time the
* application is initialized.
*
* @author Dan Jemiolo (danj)
*
*/
public abstract class AbstractFilePersistence implements Persistence
{
//
// Used to look up all exception messages
//
private static Messages _MESSAGES = MessagesFactory.get(AbstractFilePersistence.class);
//
// N -> EPR of resource whose state (or partial state) is being saved. here,
// N is a monotonically-increasing integer
//
private Map _fileNumbersByEPR = new HashMap();
//
// maps to the <persistence><location/></persistence> element in muse.xml.
// here, "location" is the directory that contains sub-directories with
// the state/partial-state of resource instances
//
private String _location = null;
//
// provides access to all of the resource instances that exist at runtime
// so we can add and compare to that set
//
private ResourceManager _manager = null;
//
// name-value pairs specified with <init-param/>
//
private Map _parameters = null;
/**
*
* Creates the proper file name for the given resource instance and then
* delegates creation of the file's contents to the abstract method of
* the same name.
*
* @param epr
* @param resource
*
* @see #getNextFileNumber()
* @see #createResourceFile(EndpointReference, Resource, File)
*
*/
protected void createResourceFile(EndpointReference epr, Resource resource)
throws SoapFault
{
int nextNumber = getNextFileNumber();
String nextFileName = getNextFileName(nextNumber);
File resourceTypeDir = getResourceTypeDirectory(resource.getContextPath());
File resourceFile = new File(resourceTypeDir, nextFileName);
createResourceFile(epr, resource, resourceFile);
getFileNumbersByEPR().put(epr, new Integer(nextNumber));
}
/**
*
* This method should be overridden by concrete file-based persistence
* classes to create the given file and fill it with the appropriate
* XML content. Classes that are only interested in the serialization of
* a certain capability's data can use the Resource object to get access
* to said capability.
*
* @param epr
* The EPR that maps to the given Resource in the ResourceManager.
*
* @param resource
* The resource instance whose state is being persisted.
*
* @param resourceFile
* The File object that represents the yet-to-be-created XML file
* that will contain the content generated by this method. The
* implementation of this method must be sure to create the File
* on disk somehow.
*
* @throws SoapFault
* <ul>
* <li>If there is an error generating the proper content for the
* persistence file.</li>
* <li>If there is an I/O error while reading or writing to the
* file system.</li>
* </ul>
*
*/
protected abstract void createResourceFile(EndpointReference epr, Resource resource, File resourceFile)
throws SoapFault;
/**
*
* Finds the file associated with the given resource EPR and removes it
* from the file system. This method should be called when a resource is
* destroyed (gone forever), but not when it is merely shutdown (because
* of server reboot, etc.).
*
* @param epr
* The EPR of the resource that has been permanently destroyed.
*
*/
protected void destroyResourceFile(EndpointReference epr)
throws SoapFault
{
Integer fileNumber = (Integer)getFileNumbersByEPR().get(epr);
FileNumberFilter filter = new FileNumberFilter(fileNumber);
String contextPath = getContextPath(epr);
File resourceTypeDir = getResourceTypeDirectory(contextPath);
File[] results = resourceTypeDir.listFiles(filter);
//
// make sure we're not trying to delete state that doesn't exist,
// which won't cause an immediate error but may point to problems
// in the persistence impl
//
if (results.length == 0)
{
Object[] filler = { "\n\n" + epr };
throw new SoapFault(_MESSAGES.get("NoFileForEPR", filler));
}
results[0].delete();
getFileNumbersByEPR().remove(epr);
}
/**
*
* @param epr
*
* @return The last token after the last slash in the EPR's address. That
* is, for an EPR with address http://example.com/my-resource,
* the method returns 'my-resource'.
*
*/
protected String getContextPath(EndpointReference epr)
{
String addressPath = epr.getAddress().getPath();
int slash = addressPath.lastIndexOf('/');
return addressPath.substring(slash + 1);
}
/**
*
* @param fileName
*
* @return The number at the end of the file name, before the suffix. That
* is, for a file named 'my-file-14.xml', the method returns 14.
*
*/
protected Integer getFileNumber(String fileName)
{
int underscore = fileName.lastIndexOf('-');
int extension = fileName.lastIndexOf('.');
String numberString = fileName.substring(underscore + 1, extension);
return new Integer(numberString);
}
protected Map getFileNumbersByEPR()
{
return _fileNumbersByEPR;
}
/**
*
* @return The common string that will start all files created by the
* persistence implementation. This string will have the next
* file number appended to it in order to create unique file names.
*
* @see #getNextFileNumber()
*
*/
protected abstract String getFilePrefix();
public String getInitializationParameter(String name)
{
return (String)getInitializationParameters().get(name);
}
public Map getInitializationParameters()
{
return _parameters;
}
/**
*
* @param fileNumber
*
* @return A string of the following format: {file-prefix}{file-number}.xml
*
*/
protected String getNextFileName(int fileNumber)
{
return getFilePrefix() + fileNumber + ".xml";
}
/**
*
* @return The next number that is available for creation of unique file
* names; this number is determined by taking the largest number
* currently used and incrementing it by one. The method does not
* attempt to reuse numbers in 'gaps' caused by deletions (that is,
* if numbers 1, 2, 5, and 6 are used, the method returns 7, not 3).
*
*/
protected int getNextFileNumber()
{
if (getFileNumbersByEPR().isEmpty())
return 1;
//
// create a binary tree of numbers, pick the last one (= largest)
//
TreeSet sortedNumbers = new TreeSet(getFileNumbersByEPR().values());
Integer largest = (Integer)sortedNumbers.last();
return largest.intValue() + 1;
}
/**
*
* @return The File directory that was specified as the persistence location
* in muse.xml. The directory may not exist, so use File.exists()
* and/or File.mkdirs() to prevent I/O errors.
*
*/
protected File getPersistenceDirectory()
{
String path = getPersistenceLocation();
if (path == null)
throw new RuntimeException(_MESSAGES.get("NoPersistenceLocation"));
File workingDir = getResourceManager().getEnvironment().getRealDirectory();
return new File(workingDir, path);
}
public String getPersistenceLocation()
{
return _location;
}
public ResourceManager getResourceManager()
{
return _manager;
}
/**
*
* @param contextPath
*
* @return Returns a directory for a given resource type, under the
* specified persistence location. The name of the directory
* is the context path provided. If the directory does not
* exist, this method will create it before returning the
* File object.
*
* @see #getPersistenceDirectory()
* @see File#mkdirs()
*
*/
protected File getResourceTypeDirectory(String contextPath)
{
File dir = new File(getPersistenceDirectory(), contextPath);
if (!dir.exists())
dir.mkdirs();
return dir;
}
/**
*
* This implementation re-loads all saved instances of the resource types
* found in the ResourceManager. It delegates the actual reloading work
* to reloadResources(String, File).
*
* @see #reloadResources(String, File)
*
*/
public void reload()
throws SoapFault
{
Iterator i = getResourceManager().getResourceContextPaths().iterator();
while (i.hasNext())
{
String contextPath = (String)i.next();
File resourceTypeDir = getResourceTypeDirectory(contextPath);
reloadResources(contextPath, resourceTypeDir);
}
}
/**
*
* This method should be overridden by concrete file-based persistence
* classes to update a resource instance with the saved data from the
* XML fragment. The resource instance may be created by this method, or
* it may exist prior to invocation.
*
* @param contextPath
* The context path of the instance's resource type.
*
* @param resourceXML
* The persisted data that must be reloaded.
*
* @return The Resource instance whose state (or part of it) has been
* reloaded from XML. The Resource may have already existed
* prior to the method being called, and simply had one of its
* capabilities updated with saved data; it may also have been
* created by the method and initialized right before return.
*
*/
protected abstract Resource reloadResource(String contextPath, Element resourceXML)
throws SoapFault;
/**
*
* This method finds all of the files in the resource type's persistence
* directory and reloads them one at a time. It delegates the reloading of
* individual resource instances to reloadResource(String, Element).
*
* @param contextPath
* @param resourceTypeDir
*
* @see #reloadResource(String, Element)
*
*/
protected void reloadResources(String contextPath, File resourceTypeDir)
throws SoapFault
{
File[] resourceFiles = resourceTypeDir.listFiles(new ResourceFileFilter());
for (int n = 0; n < resourceFiles.length; ++n)
{
Document xmlDoc = null;
try
{
xmlDoc = XmlUtils.createDocument(resourceFiles[n]);
}
catch (Throwable error)
{
throw new RuntimeException(error.getMessage(), error);
}
Element root = XmlUtils.getFirstElement(xmlDoc);
Resource resource = reloadResource(contextPath, root);
String fileName = resourceFiles[n].getName();
Integer fileNumber = getFileNumber(fileName);
getFileNumbersByEPR().put(resource.getEndpointReference(), fileNumber);
}
}
public void setInitializationParameters(Map parameters)
{
_parameters = parameters;
}
public void setPersistenceLocation(String location)
{
_location = location;
}
public void setResourceManager(ResourceManager manager)
{
_manager = manager;
}
/**
*
* FileNumberFilter finds files that end with a given number (excluding
* the file suffix).
*
* @author Dan Jemiolo (danj)
*
*/
private class FileNumberFilter implements FileFilter
{
private Integer _fileNumber = null;
public FileNumberFilter(Integer fileNumber)
{
_fileNumber = fileNumber;
}
public boolean accept(File file)
{
return file.getName().indexOf("-" + _fileNumber + ".xml") >= 0;
}
}
/**
*
* ResourceFileFilter finds files that start with the value returned by
* getFilePrefix().
*
* @author Dan Jemiolo (danj)
*
*/
private class ResourceFileFilter implements FileFilter
{
public boolean accept(File file)
{
return file.getName().startsWith(getFilePrefix());
}
}
}