org.efaps.db.store.VFSStoreResource Maven / Gradle / Ivy
/*
* Copyright 2003 - 2012 The eFaps Team
*
* 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.
*
* Revision: $Rev: 7483 $
* Last Changed: $Date: 2012-05-11 11:57:38 -0500 (Fri, 11 May 2012) $
* Last Changed By: $Author: [email protected] $
*/
package org.efaps.db.store;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipOutputStream;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NameClassPair;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.transaction.xa.XAException;
import javax.transaction.xa.Xid;
import org.apache.commons.vfs.FileContent;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.impl.DefaultFileSystemManager;
import org.apache.commons.vfs.provider.FileProvider;
import org.efaps.db.Instance;
import org.efaps.db.wrapper.SQLSelect;
import org.efaps.util.EFapsException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The class implements the {@link Resource} interface for Apache Jakarta
* Commons Virtual File System.
*
* All different virtual file systems could be used. The algorithm is:
*
* - check if the file already exists
*
*
*
* The store implements the compress property setting on the type for
* ZIP
and GZIP
.
*
* For each file id a new VFS store resource must be created.
*
* @author The eFaps Team
* @version $Id: VFSStoreResource.java 7483 2012-05-11 16:57:38Z [email protected] $
*/
public class VFSStoreResource
extends AbstractStoreResource
{
/**
* Extension of the temporary file in the store used in the transaction
* that the original file is not overwritten.
*/
private static final String EXTENSION_TEMP = ".tmp";
/**
* Extension of a file in the store.
*/
private static final String EXTENSION_NORMAL = "";
/**
* Extension of a bakup file in the store.
*/
private static final String EXTENSION_BACKUP = ".bak";
/**
* Property Name of the number of sub directories.
*/
private static final String PROPERTY_NUMBER_SUBDIRS = "VFSNumberSubDirectories";
/**
* Property Name to define if the type if is used to define a sub
* directory.
*/
private static final String PROPERTY_USE_TYPE = "VFSUseTypeIdInPath";
/**
* Property Name to define if the type if is used to define a sub
* directory.
*/
private static final String PROPERTY_NUMBER_BACKUP = "VFSNumberBackups";
/**
* Property Name to define the base name.
*/
private static final String PROPERTY_BASENAME = "VFSBaseName";
/**
* Property Name for the class name of the Provider.
*/
private static final String PROPERTY_PROVIDER = "VFSProvider";
/**
* Logging instance used in this class.
*/
private static final Logger LOG = LoggerFactory.getLogger(VFSStoreResource.class);
/**
* Buffer used to copy from the input stream to the output stream.
*
* @see #write(InputStream, int)
*/
private final byte[] buffer = new byte[1024];
/**
* Stores the name of the file including the correct directory.
*/
private String storeFileName = null;
/**
* FilesystemManager for this VFSStoreResource.
*/
private DefaultFileSystemManager manager;
/**
* NUmber of backup files to be kept.
*/
private int numberBackup = 1;
/**
* Method called to initialize this StoreResource.
* @param _instance Instance of the object this StoreResource is wanted
* for
* @param _store Store this resource belongs to
* @throws EFapsException on error
* @see Resource#initialize(Instance, Map, Compress)
*/
@Override
public void initialize(final Instance _instance,
final Store _store)
throws EFapsException
{
super.initialize(_instance, _store);
final StringBuilder fileNameTmp = new StringBuilder();
final String useTypeIdStr = getStore().getResourceProperties().get(VFSStoreResource.PROPERTY_USE_TYPE);
if ("true".equalsIgnoreCase(useTypeIdStr)) {
fileNameTmp.append(getInstance().getType().getId()).append("/");
}
final String numberSubDirsStr = getStore().getResourceProperties().get(
VFSStoreResource.PROPERTY_NUMBER_SUBDIRS);
if (numberSubDirsStr != null) {
final long numberSubDirs = Long.parseLong(numberSubDirsStr);
final String pathFormat = "%0"
+ Math.round(Math.log10(numberSubDirs) + 0.5d)
+ "d";
fileNameTmp.append(String.format(pathFormat,
getInstance().getId() % numberSubDirs))
.append("/");
}
fileNameTmp.append(getInstance().getType().getId()).append(".").append(getInstance().getId());
this.storeFileName = fileNameTmp.toString();
final String numberBackupStr = getStore().getResourceProperties().get(VFSStoreResource.PROPERTY_NUMBER_BACKUP);
if (numberBackupStr != null) {
this.numberBackup = Integer.parseInt(numberBackupStr);
}
if (this.manager == null) {
try {
DefaultFileSystemManager tmpMan = null;
if (getStore().getResourceProperties().containsKey(Store.PROPERTY_JNDINAME)) {
final InitialContext initialContext = new InitialContext();
final Context context = (Context) initialContext.lookup("java:comp/env");
final NamingEnumeration nameEnum = context.list("");
while (nameEnum.hasMoreElements()) {
final NameClassPair namePair = nameEnum.next();
if (namePair.getName().equals(getStore().getResourceProperties().get(
Store.PROPERTY_JNDINAME))) {
tmpMan = (DefaultFileSystemManager) context.lookup(
getStore().getResourceProperties().get(Store.PROPERTY_JNDINAME));
break;
}
}
}
if (tmpMan == null && this.manager == null) {
this.manager = evaluateFileSystemManager();
}
} catch (final NamingException e) {
throw new EFapsException(VFSStoreResource.class, "initialize.NamingException", e);
}
}
}
/**
* @return DefaultFileSystemManager
* @throws EFapsException on error
*/
private DefaultFileSystemManager evaluateFileSystemManager()
throws EFapsException
{
final DefaultFileSystemManager ret = new DefaultFileSystemManager();
final String baseName = getProperties().get(VFSStoreResource.PROPERTY_BASENAME);
final String provider = getProperties().get(VFSStoreResource.PROPERTY_PROVIDER);
try {
ret.init();
final FileProvider fileProvider = (FileProvider) Class.forName(provider).newInstance();
ret.addProvider(baseName, fileProvider);
ret.setBaseFile(fileProvider.findFile(null, baseName, null));
} catch (final FileSystemException e) {
throw new EFapsException(VFSStoreResource.class,
"evaluateFileSystemManager.FileSystemException",
e, provider, baseName);
} catch (final InstantiationException e) {
throw new EFapsException(VFSStoreResource.class,
"evaluateFileSystemManager.InstantiationException",
e, baseName, provider);
} catch (final IllegalAccessException e) {
throw new EFapsException(VFSStoreResource.class,
"evaluateFileSystemManager.IllegalAccessException",
e, provider);
} catch (final ClassNotFoundException e) {
throw new EFapsException(VFSStoreResource.class,
"evaluateFileSystemManager.ClassNotFoundException",
e, provider);
}
return ret;
}
/**
* {@inheritDoc}
*/
@Override
protected int add2Select(final SQLSelect _select)
{
return 0;
}
/**
* The method writes the context (from the input stream) to a temporary file
* (same file URL, but with extension {@link #EXTENSION_TEMP}).
*
* @param _in input stream defined the content of the file
* @param _size length of the content (or negative meaning that the length
* is not known; then the content gets the length of readable
* bytes from the input stream)
* @param _fileName name of the file
* @return size of the created temporary file object
* @throws EFapsException on error
*/
public long write(final InputStream _in,
final long _size,
final String _fileName)
throws EFapsException
{
try {
long size = _size;
final FileObject tmpFile = this.manager.resolveFile(this.manager.getBaseFile(),
this.storeFileName + VFSStoreResource.EXTENSION_TEMP);
if (!tmpFile.exists()) {
tmpFile.createFile();
}
final FileContent content = tmpFile.getContent();
OutputStream out = content.getOutputStream(false);
if (getCompress().equals(Compress.GZIP)) {
out = new GZIPOutputStream(out);
} else if (getCompress().equals(Compress.ZIP)) {
out = new ZipOutputStream(out);
}
// if size is unkown!
if (_size < 0) {
int length = 1;
size = 0;
while (length > 0) {
length = _in.read(this.buffer);
if (length > 0) {
out.write(this.buffer, 0, length);
size += length;
}
}
} else {
Long length = _size;
while (length > 0) {
final int readLength = length.intValue() < this.buffer.length
? length.intValue() : this.buffer.length;
_in.read(this.buffer, 0, readLength);
out.write(this.buffer, 0, readLength);
length -= readLength;
}
}
if (getCompress().equals(Compress.GZIP) || getCompress().equals(Compress.ZIP)) {
out.close();
}
tmpFile.close();
setFileInfo(_fileName, size);
return size;
} catch (final IOException e) {
VFSStoreResource.LOG.error("write of content failed", e);
throw new EFapsException(VFSStoreResource.class, "write.IOException", e);
}
}
/**
* Deletes the file defined in {@link #fileId}.
*/
public void delete()
{
//Deletion is done on commit
}
/**
* Returns for the file the input stream.
*
* @return input stream of the file with the content
* @throws EFapsException on error
*/
public InputStream read()
throws EFapsException
{
StoreResourceInputStream in = null;
try {
final FileObject file = this.manager.resolveFile(this.storeFileName + VFSStoreResource.EXTENSION_NORMAL);
if (!file.isReadable()) {
VFSStoreResource.LOG.error("file for " + this.storeFileName + " not readable");
throw new EFapsException(VFSStoreResource.class, "#####file not readable");
}
in = new VFSStoreResourceInputStream(this, file);
} catch (final FileSystemException e) {
VFSStoreResource.LOG.error("read of " + this.storeFileName + " failed", e);
throw new EFapsException(VFSStoreResource.class, "read.Throwable", e);
} catch (final IOException e) {
VFSStoreResource.LOG.error("read of " + this.storeFileName + " failed", e);
throw new EFapsException(VFSStoreResource.class, "read.Throwable", e);
}
return in;
}
/**
* Ask the resource manager to prepare for a transaction commit of the
* transaction specified in xid (used for 2-phase commits).
*
* @param _xid Xid
* @return always 0
*/
public int prepare(final Xid _xid)
{
if (VFSStoreResource.LOG.isDebugEnabled()) {
VFSStoreResource.LOG.debug("prepare (xid=" + _xid + ")");
}
return 0;
}
/**
* Method that deletes the oldest backup and moves the others one up.
*
* @param _backup file to backup
* @param _number number of backup
* @throws FileSystemException on error
*/
private void backup(final FileObject _backup,
final int _number)
throws FileSystemException
{
if (_number < this.numberBackup) {
final FileObject backFile = this.manager.resolveFile(this.manager.getBaseFile(),
this.storeFileName + VFSStoreResource.EXTENSION_BACKUP + _number);
if (backFile.exists()) {
backup(backFile, _number + 1);
}
_backup.moveTo(backFile);
} else {
_backup.delete();
}
}
/**
* The method is called from the transaction manager if the complete
* transaction is completed.
* A file in the virtual file system is committed with the algorithms:
*
* - any existing backup fill will be moved to an older backup file. The
* maximum number of backups can be defined by setting the property
* {@link #PROPERTY_NUMBER_BACKUP}. Default is one. To disable the
* property must be set to 0.
* - the current file is moved to the backup file (or deleted if property
* {@link #PROPERTY_NUMBER_BACKUP} is 0)
* - the new file is moved to the original name
*
*
* @param _xid global transaction identifier (not used, because each
* file with the file id gets a new VFS store resource
* instance)
* @param _onePhase true if it is a one phase commitment transaction
* (not used)
* @throws XAException if any exception occurs (catch on
* {@link java.lang.Throwable})
*/
public void commit(final Xid _xid,
final boolean _onePhase)
throws XAException
{
if (VFSStoreResource.LOG.isDebugEnabled()) {
VFSStoreResource.LOG.debug("transaction commit");
}
if (getStoreEvent() == VFSStoreResource.StoreEvent.WRITE) {
try {
final FileObject tmpFile = this.manager.resolveFile(this.manager.getBaseFile(),
this.storeFileName + VFSStoreResource.EXTENSION_TEMP);
final FileObject currentFile = this.manager.resolveFile(this.manager.getBaseFile(),
this.storeFileName + VFSStoreResource.EXTENSION_NORMAL);
final FileObject bakFile = this.manager.resolveFile(this.manager.getBaseFile(),
this.storeFileName + VFSStoreResource.EXTENSION_BACKUP);
if (bakFile.exists() && (this.numberBackup > 0)) {
backup(bakFile, 0);
}
if (currentFile.exists()) {
if (this.numberBackup > 0) {
currentFile.moveTo(bakFile);
} else {
currentFile.delete();
}
}
tmpFile.moveTo(currentFile);
tmpFile.close();
currentFile.close();
bakFile.close();
} catch (final FileSystemException e) {
VFSStoreResource.LOG.error("transaction commit fails for " + _xid
+ " (one phase = " + _onePhase + ")", e);
final XAException xa = new XAException(XAException.XA_RBCOMMFAIL);
xa.initCause(e);
throw xa;
}
} else if (getStoreEvent() == VFSStoreResource.StoreEvent.DELETE) {
try {
final FileObject curFile = this.manager.resolveFile(this.manager.getBaseFile(),
this.storeFileName + VFSStoreResource.EXTENSION_NORMAL);
final FileObject bakFile = this.manager.resolveFile(this.manager.getBaseFile(),
this.storeFileName + VFSStoreResource.EXTENSION_BACKUP);
if (bakFile.exists()) {
bakFile.delete();
}
if (curFile.exists()) {
curFile.moveTo(bakFile);
}
bakFile.close();
curFile.close();
} catch (final FileSystemException e) {
VFSStoreResource.LOG.error("transaction commit fails for " + _xid
+ " (one phase = " + _onePhase + ")", e);
final XAException xa = new XAException(XAException.XA_RBCOMMFAIL);
xa.initCause(e);
throw xa;
}
}
}
/**
* If the file written in the virtual file system must be rolled back, only
* the created temporary file (created from method {@link #write}) is
* deleted.
*
* @param _xid global transaction identifier (not used, because each
* file with the file id gets a new VFS store resource
* instance)
* @throws XAException if any exception occurs (catch on
* {@link java.lang.Throwable})
*/
public void rollback(final Xid _xid)
throws XAException
{
if (VFSStoreResource.LOG.isDebugEnabled()) {
VFSStoreResource.LOG.debug("rollback (xid = " + _xid + ")");
}
try {
final FileObject tmpFile = this.manager.resolveFile(this.manager.getBaseFile(),
this.storeFileName + VFSStoreResource.EXTENSION_TEMP);
if (tmpFile.exists()) {
tmpFile.delete();
}
} catch (final FileSystemException e) {
VFSStoreResource.LOG.error("transaction rollback fails for " + _xid, e);
final XAException xa = new XAException(XAException.XA_RBCOMMFAIL);
xa.initCause(e);
throw xa;
}
}
/**
* Tells the resource manager to forget about a heuristically completed
* transaction branch.
*
* @param _xid global transaction identifier (not used, because each file
* with the file id gets a new VFS store resource instance)
*/
public void forget(final Xid _xid)
{
if (VFSStoreResource.LOG.isDebugEnabled()) {
VFSStoreResource.LOG.debug("forget (xid = " + _xid + ")");
}
}
/**
* Obtains the current transaction timeout value set for this XAResource
* instance.
*
* @return always 0
*/
public int getTransactionTimeout()
{
if (VFSStoreResource.LOG.isDebugEnabled()) {
VFSStoreResource.LOG.debug("getTransactionTimeout");
}
return 0;
}
/**
* Obtains a list of prepared transaction branches from a resource manager.
*
* @param _flag flag
* @return always null
*/
public Xid[] recover(final int _flag)
{
if (VFSStoreResource.LOG.isDebugEnabled()) {
VFSStoreResource.LOG.debug("recover (flag = " + _flag + ")");
}
return null;
}
/**
* Sets the current transaction timeout value for this XAResource instance.
*
* @param _seconds number of seconds
* @return always true
*/
public boolean setTransactionTimeout(final int _seconds)
{
if (VFSStoreResource.LOG.isDebugEnabled()) {
VFSStoreResource.LOG.debug("setTransactionTimeout (seconds = " + _seconds + ")");
}
return true;
}
/**
* Input stream wrapper class.
*/
private class VFSStoreResourceInputStream
extends StoreResourceInputStream
{
/**
* File to be stored.
*/
private final FileObject file;
/**
* @param _storeRes storeresource
* @param _file file to store
* @throws IOException on error
*/
protected VFSStoreResourceInputStream(final AbstractStoreResource _storeRes,
final FileObject _file)
throws IOException
{
super(_storeRes, _file.getContent().getInputStream());
this.file = _file;
}
/**
* The file object {@link #file} is closed. The method overwrites the
* method to close the input stream, because if the file is closed, the
* input stream is also closed.
*
* @throws IOException on error
*/
@Override
protected void beforeClose() throws IOException
{
this.file.close();
}
}
}