All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.efaps.db.store.VFSStoreResource Maven / Gradle / Ivy

Go to download

eFaps is a framework used to map objects with or without attached files to a relational database and optional file systems (only for attaches files). Configurable access control can be provided down to object and attribute level depending on implementation and use case. Depending on requirements, events (like triggers) allow to implement business logic and to separate business logic from user interface. The framework includes integrations (e.g. webdav, full text search) and a web application as 'simple' configurable user interface. Some best practises, example web application modules (e.g. team work module) support administrators and implementers using this framework.

There is a newer version: 3.2.0
Show newest version
/*
 * 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: *

    *
  1. check if the file already exists
  2. *
  3. *
  4. *
* 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: *
    *
  1. 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.
  2. *
  3. the current file is moved to the backup file (or deleted if property * {@link #PROPERTY_NUMBER_BACKUP} is 0)
  4. *
  5. the new file is moved to the original name
  6. *
* * @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(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy