/*
* Copyright (C) 2009 eXo Platform SAS.
*
* 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.exoplatform.services.jcr.impl.storage.value.fs.operations;
import org.exoplatform.services.jcr.impl.storage.value.ValueDataResourceHolder;
import org.exoplatform.services.jcr.impl.storage.value.ValueOperation;
import org.exoplatform.services.jcr.impl.storage.value.fs.FileLockException;
import org.exoplatform.services.jcr.impl.util.io.FileCleaner;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* Created by The eXo Platform SAS.
*
* <br>
* Date: 03.04.2009
*
* @author <a href="mailto:peter.nedonosko@exoplatform.com.ua">Peter Nedonosko</a>
* @version $Id: ValueFileOperation.java 111 2008-11-11 11:11:11Z pnedonosko $
*/
public abstract class ValueFileOperation extends ValueFileIOHelper implements ValueOperation
{
/**
* Temporary files extension.
*/
public static final String TEMP_FILE_EXTENSION = ".temp";
/**
* Lock files extension.
*/
public static final String LOCK_FILE_EXTENSION = ".lock";
/**
* Logger.
*/
protected static final Log LOG = ExoLogger.getLogger("exo.jcr.component.core.ValueFileOperation");
/**
* The local internet address
*/
private static String LOCAL_ADDRESS;
static
{
try
{
// get the inet address
InetAddress local = InetAddress.getLocalHost();
LOCAL_ADDRESS = local.getHostAddress() + " (" + local.getHostName() + ")";
}
catch (UnknownHostException e)
{
LOG.warn("Cannot read host address " + e.getMessage());
LOCAL_ADDRESS = "no address, " + e;
}
}
/**
* File cleaner.
*/
protected final FileCleaner cleaner;
/**
* Resources resources.
*/
protected final ValueDataResourceHolder resources;
/**
* Operation info (not used).
*/
protected final String operationInfo;
/**
* Directory for temporary operations (temp files and locks).
*/
protected final File tempDir;
/**
* Performed state flag. Optional for use.
*/
private boolean performed = false;
/**
* Internal ValueLockSupport implementation.
*
*/
class ValueFileLockHolder implements ValueLockSupport
{
/**
* Target File.
*/
private final File targetFile;
/**
* Lock File.
*/
private File lockFile;
/**
* Lock File stream.
*/
private FileOutputStream lockFileStream;
/**
* ValueFileLockHolder constructor.
*
* @param file
* target File
*/
ValueFileLockHolder(File file)
{
this.targetFile = file;
}
/**
* {@inheritDoc}
*/
public void lock() throws IOException
{
// lock file in temp directory
lockFile = new File(tempDir, targetFile.getName() + LOCK_FILE_EXTENSION);
FileOutputStream lout = new FileOutputStream(lockFile, true);
lout.write(operationInfo.getBytes());
lout.getChannel().lock(); // wait for unlock (on Windows will wait for this JVM too)
lockFileStream = lout;
}
/**
* {@inheritDoc}
*/
public void share(ValueLockSupport anotherLock) throws IOException
{
if (anotherLock instanceof ValueFileLockHolder)
{
ValueFileLockHolder al = (ValueFileLockHolder)anotherLock;
lockFile = al.lockFile;
lockFileStream = al.lockFileStream;
}
else
throw new IOException("Cannot share lock with " + anotherLock.getClass());
}
/**
* {@inheritDoc}
*/
public void unlock() throws IOException
{
if (lockFileStream != null)
lockFileStream.close();
if (!lockFile.delete())
{
LOG.warn("Cannot delete lock file " + lockFile.getAbsolutePath() + ". Add to the FileCleaner");
cleaner.addFile(lockFile);
}
}
}
/**
* Value File lock abstraction.
*
*/
class ValueFileLock
{
/**
* Target file.
*/
private final File file;
/**
* ValueFileLock constructor.
*
* @param file
* File
*/
ValueFileLock(File file)
{
this.file = file;
}
/**
* Lock File location (place on file-system) for this JVM and external changes.
*
* @return boolean, true - if locked, false - if already locked by this Thread
* @throws IOException
* if error occurs
*/
public boolean lock() throws IOException
{
// lock in JVM (wait for unlock if required)
try
{
return resources.aquire(file.getAbsolutePath(), new ValueFileLockHolder(file));
}
catch (InterruptedException e)
{
throw new FileLockException("Lock error on " + file.getAbsolutePath(), e);
}
}
/**
* Unlock File location (place on file-system) for this JVM and external changes.
*
* @return boolean, true - if unlocked, false - if still locked by this Thread
* @throws IOException
* if error occurs
*/
public boolean unlock() throws IOException
{
return resources.release(file.getAbsolutePath());
}
}
/**
* ValueFileOperation constructor.
*
* @param resources
* ValueDataResourceHolder
* @param cleaner
* FileCleaner
* @param tempDir
* temp dir for locking and other I/O operations
*/
ValueFileOperation(ValueDataResourceHolder resources, FileCleaner cleaner, File tempDir)
{
this.cleaner = cleaner;
this.resources = resources;
this.tempDir = tempDir;
operationInfo = System.currentTimeMillis() + " " + LOCAL_ADDRESS;
}
/**
* Tell if operation was performed.
*
* @return boolean - true if operation was performed
*/
protected boolean isPerformed()
{
return performed;
}
/**
* Make operation as performed.
*
* @throws IOException
* if operation was already performed
*/
protected void makePerformed() throws IOException
{
if (performed)
throw new IOException("Operation cannot be performed twice");
performed = true;
}
/**
* {@inheritDoc}
*/
public void commit() throws IOException
{
try
{
prepare();
}
finally
{
twoPhaseCommit();
}
}
/**
* Moves a file from a location to another by using the method {{File.renameTo(File)}}
* if it fails it will do a copy and delete. If the source file cannot be deleted
* it will be given to the file cleaner
* @param srcFile the file to be moved
* @param destFile the destination file
* @throws IOException if error occurs
*/
protected void move(File srcFile, File destFile) throws IOException
{
if (!srcFile.renameTo(destFile))
{
try
{
copyClose(new FileInputStream(srcFile), new FileOutputStream(destFile));
}
catch (IOException e)
{
throw new IOException("Could not move the file from " + srcFile.getAbsolutePath() + " to "
+ destFile.getAbsolutePath(), e);
}
if (!srcFile.delete())
{
if (LOG.isDebugEnabled())
{
LOG.debug("The file '"
+ srcFile.getAbsolutePath()
+ "' could not be deleted which prevents the application"
+ " to move it properly, it is probably due to a stream used to read this property "
+ "that has not been closed as expected. The file will be given to the file cleaner for a later deletion.");
}
// The source could not be deleted so we add it to the
// file cleaner
cleaner.addFile(srcFile);
}
}
}
}