org.jboss.remoting.callback.CallbackStore Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.remoting.callback;
import org.jboss.logging.Logger;
import org.jboss.remoting.InvokerLocator;
import org.jboss.remoting.serialization.SerializationStreamFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Map;
/**
* Acts as a persistent list which writes Serializable objects to disk and will retrieve them
* in same order in which they were added (FIFO). Each file will be named according to the current
* time (using System.currentTimeMillis() with the file suffix specified (see below). When the
* object is read and returned by calling the getNext() method, the file on disk for that object will
* be deleted. If for some reason the store VM crashes, the objects will still be available upon next startup.
*
* The attributes to make sure to configure are:
*
* file path - this determins which directory to write the objects. The default value is the property value
* of 'jboss.server.data.dir' and if this is not set, then will be 'data'. For example, might
* be /jboss/server/default/data.
* file suffix - the file suffix to use for the file written for each object stored.
*
* This is also a service mbean, so can be run as a service within JBoss AS or stand alone.
*
* @author Tom Elrod
*/
public class CallbackStore implements CallbackStoreMBean
{
private static long previousTimestamp;
private static int timestampCounter;
private String filePath = null;
private String fileSuffix = "ser";
private boolean isStarted = false;
private boolean purgeOnShutdown = false;
private String serializationType = "java";
/**
* Key for setting which directory to write the callback objects.
* The default value is the property value of 'jboss.server.data.dir' and if this is not set,
* then will be 'data'. Will then append 'remoting' and the callback client's session id.
* An example would be 'data\remoting\5c4o05l-9jijyx-e5b6xyph-1-e5b6xyph-2'.
*/
public static final String FILE_PATH_KEY = "StoreFilePath";
/**
* Key for setting the file suffix to use for the callback objects written to disk. The default value is "ser".
*/
public static final String FILE_SUFFIX_KEY = "StoreFileSuffix";
private static final Logger log = Logger.getLogger(CallbackStore.class);
/**
* Default store constructor.
*/
public CallbackStore()
{
}
/**
* Store constructor.
*
* @param purgeOnDestroy if true, will remove all persisted objects from disk on when destroy() is called, else
* will leave the files (which is the default behaviour).
*/
public CallbackStore(boolean purgeOnDestroy)
{
this.purgeOnShutdown = purgeOnDestroy;
}
/**
* Will get the file path value (if not already set will just use the
* default setting) and will create the directory specified by the file path
* if it does not already exist.
*
* @throws Exception
*/
public void start() throws Exception
{
if (!isStarted)
{
// need to figure the best place to store on disk
if (filePath == null)
{
filePath = System.getProperty("jboss.server.data.dir", "data");
}
File storeFile = new File(filePath);
if (!storeFile.exists())
{
boolean madeDir = storeFile.mkdirs();
if (!madeDir)
{
throw new IOException("Can not create directory for store. Path given: " + filePath);
}
}
isStarted = true;
}
}
/**
* Sets if store should clean up persisted files when shutdown (destroy()).
*
* @param purgeOnShutdown
*/
public void setPurgeOnShutdown(boolean purgeOnShutdown)
{
this.purgeOnShutdown = purgeOnShutdown;
}
/**
* Returns if store will clean up persisted files when shutdown (destroy()).
*
* @return
*/
public boolean getPurgeOnShutdown()
{
return purgeOnShutdown;
}
/**
* This is a no op method, but needed in order to be used as a service within JBoss AS.
*
* @throws Exception
*/
public void create() throws Exception
{
}
/**
* This will allow for change of file suffix and file path and then may start again
* using these new values. However, any object already written out using the old
* values will be lost as will not longer be accessible if these attributes are changed while stopped.
*/
public void stop()
{
isStarted = false;
}
/**
* If purgeOnDestroy is true, will remove files upon shutdown.
*/
public void destroy()
{
if (purgeOnShutdown)
{
purgeFiles();
}
}
public void purgeFiles()
{
String[] fileList = getObjectFileList();
String fileToDelete = null;
for (int x = 0; x < fileList.length; x++)
{
try
{
fileToDelete = filePath + System.getProperty("file.separator") + fileList[x];
File currentFile = new File(fileToDelete);
boolean deleted = currentFile.delete();
if (!deleted)
{
log.warn("Error purging file " + fileToDelete);
}
}
catch (Exception e)
{
log.warn("Error purging file " + fileToDelete);
}
}
}
/**
* Will use the values in the map to set configuration. This will not change behaviour of store until
* has been stopped and then started (if has not been started, will take effect upon start).
* The keys for the map are FILE_PATH_KEY and FILE_SUFFIX_KEY.
*
* @param config
*/
public void setConfig(Map config)
{
if (config != null)
{
String newFilePath = (String) config.get(FILE_PATH_KEY);
if (newFilePath != null)
{
filePath = newFilePath;
}
String newFileSuffix = (String) config.get(FILE_SUFFIX_KEY);
if (newFileSuffix != null)
{
fileSuffix = newFileSuffix;
}
String newSerializationType = (String) config.get(InvokerLocator.SERIALIZATIONTYPE);
if (newSerializationType != null)
{
serializationType = newSerializationType;
}
}
}
/**
* Gets the file path for the directory where the objects will be stored.
*
* @return
*/
public String getStoreFilePath()
{
return filePath;
}
/**
* Sets teh file path for the directory where the objects will be stored.
*
* @param filePath
*/
public void setStoreFilePath(String filePath)
{
this.filePath = filePath;
}
/**
* Gets the file suffix for each of the files that objects will be persisted to.
*
* @return
*/
public String getStoreFileSuffix()
{
return fileSuffix;
}
/**
* Sets the file suffix for each of the files that objects will be persisted to.
*
* @param fileSuffix
*/
public void setStoreFileSuffix(String fileSuffix)
{
this.fileSuffix = fileSuffix;
}
/**
* Getst the number of objects stored and available.
*
* @return
*/
public int size()
{
verifyStarted();
String[] objectFileList = getObjectFileList();
if (objectFileList != null)
{
return objectFileList.length;
}
else
{
return 0;
}
}
private void verifyStarted()
{
if (!isStarted)
{
throw new RuntimeException("Can not call upon this store method before it has been started.");
}
}
/**
* Will look through the files in the store directory for the oldest object serialized to disk, load it,
* delete the file, and return the deserialized object.
* Important to note that once this object is returned from this method, it is gone forever from this
* store and will not be able to retrieve it again without adding it back.
*
* @return
* @throws IOException
*/
public Object getNext() throws IOException
{
verifyStarted();
Object obj = null;
String objectFilePath = null;
synchronized (filePath)
{
String[] objectFileList = getObjectFileList();
FileInputStream inFile = null;
ObjectInputStream in = null;
if (objectFileList != null && objectFileList.length > 0)
{
try
{
// only getting the first one, which will be first one entered since the getting
// of the list is automatically ordered by the OS and all file names are numeric by time.
objectFilePath = filePath + System.getProperty("file.separator") + objectFileList[0];
inFile = new FileInputStream(objectFilePath);
in = SerializationStreamFactory.getManagerInstance(serializationType).createRegularInput(inFile);
try
{
obj = in.readObject();
}
catch (ClassNotFoundException e)
{
throw new IOException("Error loading persisted object. Could not load class (" + e.getMessage() + ").");
}
}
finally
{
if (inFile != null)
{
try
{
inFile.close();
}
catch (IOException ioe)
{
log.debug("Error closing FileInputStream.", ioe);
}
}
if (in != null)
{
try
{
in.close();
}
catch (IOException ioe)
{
log.debug("Error closing ObjectInputStream.", ioe);
}
}
if (objectFilePath != null)
{
// now remove the file
File objectFile = new File(objectFilePath);
boolean isDeleted = objectFile.delete();
if (log.isTraceEnabled())
{
log.trace("object file (" + objectFilePath + ") has been deleted - " + isDeleted);
}
}
}
}
}
return obj;
}
private String[] getObjectFileList()
{
File storePath = new File(filePath);
String[] objectFileList = storePath.list(new StoreFileFilter());
Arrays.sort(objectFileList);
return objectFileList;
}
/**
* Persists the serializable object passed to the directory specified. The file name will be the current time
* in milliseconds (vis System.currentTimeMillis()) with the specified suffix. This object can later be
* retrieved using the getNext() method, but objects will be returned in the order that they were added (FIFO).
*
* @param object
* @throws IOException
*/
public void add(Serializable object) throws IOException
{
verifyStarted();
synchronized (filePath)
{
long currentTimestamp = System.currentTimeMillis();
if (previousTimestamp == currentTimestamp)
{
timestampCounter++;
}
else
{
previousTimestamp = currentTimestamp;
timestampCounter = 0;
}
StringBuffer path = new StringBuffer(filePath);
path.append(System.getProperty("file.separator")).append(String.valueOf(currentTimestamp));
path.append("-").append(timestampCounter).append(".").append(fileSuffix);
File storeFile = new File(path.toString());
FileOutputStream outFile = null;
ObjectOutputStream out = null;
try
{
outFile = new FileOutputStream(storeFile, false);
out = SerializationStreamFactory.getManagerInstance(serializationType).createOutput(outFile);
out.writeObject(object);
out.flush();
}
finally
{
if (outFile != null)
{
try
{
outFile.close();
}
catch (IOException ioe)
{
log.debug("Error closing FileInputStream.", ioe);
}
}
if (out != null)
{
try
{
out.close();
}
catch (IOException ioe)
{
log.debug("Error closing ObjectInputStream.", ioe);
}
}
}
}
}
public class StoreFileFilter implements FilenameFilter
{
/**
* Tests if a specified file should be included in a file list.
*
* @param dir the directory in which the file was found.
* @param name the name of the file.
* @return true
if and only if the name should be included in the file list; false
* otherwise.
*/
public boolean accept(File dir, String name)
{
if (name.endsWith(fileSuffix))
{
return true;
}
else
{
return false;
}
}
}
}