/* * 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); } } } }