/*
* 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.xcmis.search.lucene.index;
import java.io.BufferedOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import org.xcmis.spi.utils.Logger;
/**
* Created by The eXo Platform SAS.
*
* @author <a href="mailto:Sergey.Kabashnyuk@gmail.com">Sergey Kabashnyuk</a>
* @version $Id: FileSystemTransactionLog.java 2 2010-02-04 17:21:49Z andrew00x $
*/
public class FileSystemTransactionLog implements TransactionLog
{
public static final int LOG_DIR_COUNT = 2;
private static final int ADDED_DOCUMENTS = 1;
/**
* Class logger.
*/
private static final Logger LOG = Logger.getLogger(FileSystemTransactionLog.class);
private static final int REMOVED_DOCUMENTS = 3;
private static final int UUID_SIZE = 32;
private Set<String> addedDocumentsUuids; // =
private File file;
private Set<String> removedDocumentsUuids; // =
private final FSIndexTransactionService storage;
/**
* Constructor for recovery case.
*
* @param srcFile - source file.
* @param storage - transaction storage.
*/
public FileSystemTransactionLog(final File srcFile, final FSIndexTransactionService storage)
throws TransactionLogException
{
this.storage = storage;
this.file = srcFile;
if (!this.file.exists())
{
throw new TransactionLogException("File not exist " + this.file.getAbsolutePath());
}
}
/**
* Constructor for ordinary case.
*
* @param addedDocumentsUuids added documents
* @param removedDocumentsUuids removed documents
* @param storage - transaction storage.
*/
public FileSystemTransactionLog(final Set<String> addedDocumentsUuids, final Set<String> removedDocumentsUuids,
final FSIndexTransactionService storage)
{
this.addedDocumentsUuids = addedDocumentsUuids;
this.removedDocumentsUuids = removedDocumentsUuids;
this.storage = storage;
}
/**
* {@inheritDoc}
*
* @throws TransactionLogException
*/
public Set<String> getAddedList() throws TransactionLogException
{
if (this.addedDocumentsUuids == null)
{
this.load();
}
return this.addedDocumentsUuids;
}
/**
* {@inheritDoc}
*
* @throws TransactionLogException
*/
public Set<String> getRemovedList() throws TransactionLogException
{
if (this.removedDocumentsUuids == null)
{
this.load();
}
return this.removedDocumentsUuids;
}
/**
* {@inheritDoc}
*/
public void load() throws TransactionLogException
{
this.addedDocumentsUuids = new HashSet<String>();
this.removedDocumentsUuids = new HashSet<String>();
FileInputStream in = null;
try
{
in = new FileInputStream(this.file);
int type;
while ((type = in.read()) != -1)
{
final byte[] buf = new byte[FileSystemTransactionLog.UUID_SIZE];
if (in.read(buf) != FileSystemTransactionLog.UUID_SIZE)
{
throw new TransactionLogException(" TransactionLog file is corrupted. Unexpected end of file: "
+ this.file.getAbsolutePath());
}
final String uuid = new String(buf);
switch (type)
{
case ADDED_DOCUMENTS :
this.addedDocumentsUuids.add(uuid);
break;
case REMOVED_DOCUMENTS :
this.removedDocumentsUuids.add(uuid);
break;
default :
throw new TransactionLogException(" TransactionLog file is corrupted. Unexpected type of record "
+ type + ". file : " + this.file.getAbsolutePath());
}
}
}
catch (final EOFException e)
{
throw new TransactionLogException(" TransactionLog file is uncomplete: " + this.file.getAbsolutePath());
}
catch (final IOException e)
{
throw new TransactionLogException("TransactionLog read exception: " + e.getMessage(), e);
}
finally
{
if (in != null)
{
try
{
in.close();
}
catch (final IOException e)
{
FileSystemTransactionLog.LOG.warn("Can not close log file " + this.file.getAbsolutePath());
}
}
}
}
/**
* {@inheritDoc}
*/
public void log() throws TransactionLogException
{
OutputStream out = null;
try
{
//TODO Check uuid
final String fileName = UUID.randomUUID().toString();
final File dir =
new File(this.storage.getStorageDir().getAbsoluteFile()
+ this.buildPathFromName(fileName, FileSystemTransactionLog.LOG_DIR_COUNT));
this.file = this.storage.getLockFactory().createFile(dir, fileName, FileSystemTransactionLog.LOG_DIR_COUNT);
out = new BufferedOutputStream(new FileOutputStream(this.file));
this.writeSet(out, this.addedDocumentsUuids, FileSystemTransactionLog.ADDED_DOCUMENTS);
this.writeSet(out, this.removedDocumentsUuids, FileSystemTransactionLog.REMOVED_DOCUMENTS);
out.flush();
}
catch (final IOException e)
{
throw new TransactionLogException("TransactionLog write exception: " + e.getMessage(), e);
}
finally
{
if (out != null)
{
try
{
out.close();
}
catch (final IOException e)
{
FileSystemTransactionLog.LOG.warn("Can not close log file" + this.file.getAbsolutePath());
}
}
}
}
/**
* {@inheritDoc}
*/
public void removeLog() throws TransactionLogException
{
if (!this.file.exists())
{
FileSystemTransactionLog.LOG.warn("TransactionLog file not exist. " + this.file.getAbsolutePath());
}
if (!this.removeLogFile(this.file))
{
throw new TransactionLogException("TransactionLog file was not delete. " + this.file.getAbsolutePath());
}
else
{
if (FileSystemTransactionLog.LOG.isDebugEnabled())
{
FileSystemTransactionLog.LOG.debug("Transaction log removed " + this.file.getAbsolutePath());
}
}
}
/**
* Create path to file using its name.
*
* @param fileName - fileName as a source to generated path
* @param dirCount - parent dirs count
* @return String - generated path to file.
*/
private String buildPathFromName(final String fileName, final int dirCount)
{
final char[] chs = fileName.toCharArray();
StringBuilder path = new StringBuilder();
for (int i = 1; i <= dirCount; i++)
{
path.append(File.separator);
path.append(chs[chs.length - i]);
}
return path.toString();
}
/**
* Remove log file. Depends from creation file algorithm. See builfPathX2C1
* method.
*
* @param file to remove.
* @return <code>true</code> if remove successfull.
*/
private boolean removeLogFile(final File file) throws TransactionLogException
{
final File dir = file.getParentFile();
if (!file.delete())
{
return false;
}
else
{
// try to delete parents
this.storage.getLockFactory().removeDirectory(dir, FileSystemTransactionLog.LOG_DIR_COUNT);
return true;
}
}
/**
* Write set of string to out with flag.
*
* @param out - OutputStream
* @param keys - Set of String to Store
* @param flag - ADDED_DOCUMENTS, UPDATED_DOCUMENTS or REMOVED_DOCUMENTS
* @throws IOException if write to file IOException occurs.
*/
private void writeSet(final OutputStream out, final Set<String> keys, final int flag) throws IOException
{
for (final String key : keys)
{
out.write(flag);
out.write(key.getBytes());
}
}
}