blob: 7f31003ef0a81012d62df308cf2efc4d6381b49a [file] [log] [blame]
/*
* Copyright 2011 Paul Merlin.
*
* 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.
*/
package org.apache.zest.library.uowfile.internal;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.zest.io.Inputs;
import org.apache.zest.io.Outputs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UoWFile
{
/* package */ static final Logger LOGGER = LoggerFactory.getLogger( "org.qi4j.library.uowfile" );
private static final int FILE_BUFFER_SIZE = 4096;
private static final AtomicLong COUNT = new AtomicLong( 0L );
private final long originalIdentity;
private final File original;
private final File current;
private final File backup;
UoWFile( File original, File workDir )
{
this.originalIdentity = original.length() + original.lastModified();
this.original = original;
long count = COUNT.incrementAndGet();
this.current = new File( workDir, original.getName() + ".current." + count );
this.backup = new File( workDir, original.getName() + ".backup." + count );
}
public File asFile()
{
return current;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder().append( UoWFile.class.getSimpleName() );
// UoWFile{parent/( original(oid->id) | current(id) | backup(id) )}
sb.append( "{" ).append( original.getParentFile().getName() ).append( "/( " ).
append( original.getName() ).append( "(" ).append( originalIdentity ).append( "->" ).append( fileTag( original ) ).append( ") | " ).
append( current.getName() ).append( "(" ).append( fileTag( current ) ).append( ") | " ).
append( backup.getName() ).append( "(" ).append( fileTag( backup ) ).
append( ") )}" );
return sb.toString();
}
void copyOriginalToCurrent()
{
if( original.exists() )
{
copy( original, current );
}
}
void apply()
throws ConcurrentUoWFileStateModificationException
{
LOGGER.trace( "Will apply changes to {}", this );
if( fileTag( current ) != originalIdentity )
{
if( fileTag( original ) != originalIdentity )
{
LOGGER.info( "Concurrent modification, original creation identity is {} and original apply identity is {}", originalIdentity, fileTag( original ) );
throw new ConcurrentUoWFileStateModificationException( this );
}
if( original.exists() )
{
move( original, backup );
}
if( current.exists() )
{
move( current, original );
}
LOGGER.debug( "Applied changes to {}", original );
}
}
void rollback()
{
if( backup.exists() )
{
if( fileTag( original ) != originalIdentity )
{
delete( original );
move( backup, original );
}
LOGGER.debug( "Restored backup to {}", original );
}
}
void cleanup()
{
if( current.exists() )
{
delete( current );
}
if( backup.exists() )
{
delete( backup );
}
}
/**
* @return OL if the file does not exist
*/
private long fileTag( File file )
{
return file.length() + file.lastModified();
}
private void copy( File source, File dest )
{
try
{
Inputs.byteBuffer( source, FILE_BUFFER_SIZE ).transferTo( Outputs.byteBuffer( dest ) );
}
catch( IOException ex )
{
throw new UoWFileException( ex );
}
}
private void delete( File file )
{
if( !file.delete() )
{
throw new UoWFileException( new IOException( "Unable to delete file " + file ) );
}
}
private void move( File source, File dest )
{
// Atomic move attempt
if( !source.renameTo( dest ) )
{
// source and dest are probably on different filesystem, fallback to a non atomic copy/move operation
copy( source, dest );
if( !source.delete() )
{
throw new UoWFileException( new IOException( "Unable to delete source file " + source
+ " after copy(move) to " + dest
+ " (rename failed before that)." ) );
}
LOGGER.warn( "Moved {} to {} using a copy/delete operation instead of an atomic move. "
+ "Are they on different filesystems?", source, dest );
}
}
}