/*
* 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.util.io;
import org.exoplatform.commons.utils.PrivilegedFileHelper;
import org.exoplatform.commons.utils.SecurityHelper;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.security.PrivilegedAction;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import org.exoplatform.services.jcr.impl.dataflow.SpoolConfig;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
/**
* Created by The eXo Platform SAS Author : Peter Nedonosko peter.nedonosko@exoplatform.com.ua
* 05.10.2007
*
* For use in persistent layer, i.e. JDBCStorageConnection (ClenableFileValueData), will be shared
* across the Workspace cache with many Sessions . Swap files creation (like in
* JDBCStorageConnection.readValueData(String, int, int)) managed by SwapFile.get(File, String)
* method. There are no way to get swap file in another way.
*
* A SwapFile extends the SpoolFile. But in runtime all swap files will be stored in global map used
* for prevent files rewriting in concurrent environment (case of issue JCR-329). Till the SwapFile
* spool operation in progress all other users who will attempt to get the file will wait the
* operation completion (java.util.concurrent.CountDownLatch used).
*
* @author <a href="mailto:peter.nedonosko@exoplatform.com.ua">Peter Nedonosko</a>
* @version $Id: SwapFile.java 11907 2008-03-13 15:36:21Z ksm $
*/
public class SwapFile extends SpoolFile
{
/**
* The serial version UID
*/
private static final long serialVersionUID = 4048760909657109754L;
/**
* The Logger.
*/
private static final Log LOG = ExoLogger.getLogger("exo.jcr.component.core.SwapFile");
/**
* In-share files database.
*/
protected static final ConcurrentMap<String, WeakReference<SwapFile>> CURRENT_SWAP_FILES =
new ConcurrentHashMap<String, WeakReference<SwapFile>>();
/**
* Spool latch.
*/
protected final AtomicReference<CountDownLatch> spoolLatch = new AtomicReference<CountDownLatch>();
/**
* swap cleaner (FileCleaner).
*/
private final FileCleaner swapCleaner;
/**
* SwapFile constructor.
*
* @param parent
* Parent File
* @param child
* File name
* @param cleaner
* File Cleaner
*/
protected SwapFile(File parent, String child, FileCleaner cleaner)
{
super(parent,child);
this.swapCleaner=cleaner;
}
/**
* Obtain SwapFile by parent file and name.
*
* @param parent
* - parent File
* @param child
* - String with file name
* @return SwapFile swap file
* @throws IOException
* I/O error
*/
public static SwapFile get(final File parent, final String child) throws IOException
{
return get(parent, child, SpoolConfig.getDefaultSpoolConfig().fileCleaner);
}
/**
* Obtain SwapFile by parent file and name.
*
* If the file was swapped before and still in use it will be returned, i.e. same object of
* java.io.File will be returned.
*
* If the file swapping (writing) now at this time the caller thread will wait till the swap
* process will be finished.
*
* @param parent
* - parent File
* @param child
* - String with file name
* @param cleaner
* - The FileCleaner
* @return SwapFile swap file
* @throws IOException
* I/O error
*/
public static SwapFile get(final File parent, final String child, FileCleaner cleaner) throws IOException
{
SwapFile newsf = new SwapFile(parent, child,cleaner);
String absPath = PrivilegedFileHelper.getAbsolutePath(newsf);
WeakReference<SwapFile> swappedRef = CURRENT_SWAP_FILES.get(absPath);
SwapFile swapped;
if (swappedRef != null && (swapped = swappedRef.get()) != null)
{
// The swap file has been registered already
do
{
// We loop until the spoolLatch is null
CountDownLatch spoolLatch = swapped.spoolLatch.get();
if (spoolLatch != null)
{
try
{
spoolLatch.await(); // wait till the file will be done
}
catch (final InterruptedException e)
{
// thinking that is ok, i.e. this thread is interrupted
throw new IOException("Swap file read error " + PrivilegedFileHelper.getAbsolutePath(swapped) + ". "
+ e)
{
@Override
public Throwable getCause()
{
return e;
}
};
}
}
}
while (!swapped.spoolLatch.compareAndSet(null, new CountDownLatch(1)));
return swapped;
}
else if (swappedRef != null)
{
// The SwapFile has been garbage collected so we remove it from the map
CURRENT_SWAP_FILES.remove(absPath, swappedRef);
}
newsf.spoolLatch.set(new CountDownLatch(1));
WeakReference<SwapFile> currentValue = CURRENT_SWAP_FILES.putIfAbsent(absPath, new WeakReference<SwapFile>(newsf));
if (currentValue != null)
{
// the swap file has been put already so we need to loop
return get(parent, child,cleaner);
}
return newsf;
}
/**
* Tell if the file already spooled - ready for use.
*
* @return boolean flag
*/
public boolean isSpooled()
{
return spoolLatch.get() == null;
}
/**
* Mark the file ready for read.
*/
public void spoolDone()
{
final CountDownLatch sl = this.spoolLatch.get();
this.spoolLatch.set(null);
sl.countDown();
}
/**
* Not applicable. Call get(File, String) method instead.
*
* @throws IOException
* I/O error
*/
public static SwapFile createTempFile(String prefix, String suffix, File directory) throws IOException
{
throw new IOException("Not applicable. Call get(File, String) method instead");
}
/**
* {@inheritDoc}
*/
@Override
protected void finalize() throws Throwable
{
try
{
delete();
}
finally
{
super.finalize();
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean delete()
{
String path = PrivilegedFileHelper.getAbsolutePath(this);
WeakReference<SwapFile> currentValue = CURRENT_SWAP_FILES.get(path);
if (currentValue == null || (currentValue.get() == this || currentValue.get() == null))
{
CURRENT_SWAP_FILES.remove(path, currentValue);
synchronized(this)
{
users.clear();
final SpoolFile sf = this;
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>()
{
public Boolean run()
{
if (sf.exists())
{
if (SwapFile.super.delete())
{
return true;
}
else if (swapCleaner != null)
{
swapCleaner.addFile(SwapFile.super.getAbsoluteFile());
}
if (LOG.isDebugEnabled())
{
LOG.debug("Could not remove swap file on finalize : "
+ PrivilegedFileHelper.getAbsolutePath(SwapFile.super.getAbsoluteFile()));
}
return false;
}
return true;
}
};
return SecurityHelper.doPrivilegedAction(action);
}
}
return false;
}
}