add a new module for file locking

git-svn-id: https://svn.apache.org/repos/asf/archiva/trunk@1550396 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Olivier Lamy 2013-12-12 11:26:40 +00:00
parent 074386d154
commit 6b23332e9d
14 changed files with 789 additions and 35 deletions

View File

@ -39,6 +39,16 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.archiva.redback.components.registry</groupId>
<artifactId>spring-registry-commons</artifactId>
@ -52,41 +62,41 @@
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<exclusions>
<exclusion>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils-core</artifactId>
</exclusion>
<!-- targeting JDK 1.4, xml parser/apis not needed -->
<exclusion>
<groupId>xerces</groupId>
<artifactId>xerces</artifactId>
</exclusion>
<exclusion>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
</exclusion>
<exclusion>
<groupId>xalan</groupId>
<artifactId>xalan</artifactId>
</exclusion>
<exclusion>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
</exclusion>
<exclusion>
<groupId>servletapi</groupId>
<artifactId>servletapi</artifactId>
</exclusion>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
<exclusion>
<groupId>ant</groupId>
<artifactId>ant-optional</artifactId>
</exclusion>
</exclusions>
<exclusions>
<exclusion>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils-core</artifactId>
</exclusion>
<!-- targeting JDK 1.4, xml parser/apis not needed -->
<exclusion>
<groupId>xerces</groupId>
<artifactId>xerces</artifactId>
</exclusion>
<exclusion>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
</exclusion>
<exclusion>
<groupId>xalan</groupId>
<artifactId>xalan</artifactId>
</exclusion>
<exclusion>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
</exclusion>
<exclusion>
<groupId>servletapi</groupId>
<artifactId>servletapi</artifactId>
</exclusion>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
<exclusion>
<groupId>ant</groupId>
<artifactId>ant-optional</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.ant</groupId>

View File

@ -0,0 +1,134 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one
~ or more contributor license agreements. See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership. The ASF licenses this file
~ to you 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.apache.archiva</groupId>
<artifactId>archiva-base</artifactId>
<version>2.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>archiva-filelock</artifactId>
<packaging>bundle</packaging>
<name>Archiva Base :: FileLock</name>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.googlecode.multithreadedtc</groupId>
<artifactId>multithreadedtc</artifactId>
<version>1.01</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
<Bundle-SymbolicName>org.apache.archiva.common</Bundle-SymbolicName>
<Bundle-Version>${project.version}</Bundle-Version>
<Export-Package>
org.apache.archiva.common*;version=${project.version}
</Export-Package>
<Import-Package>
</Import-Package>
</instructions>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<buildDirectory>${project.build.directory}</buildDirectory>
<java.io.tmpdir>${project.build.directory}</java.io.tmpdir>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.rat</groupId>
<artifactId>apache-rat-plugin</artifactId>
<configuration>
<excludes>
</excludes>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

View File

@ -0,0 +1,177 @@
package org.apache.archiva.common.filelock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
import java.nio.channels.OverlappingFileLockException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* @author Olivier Lamy
*/
@Service("fileLockManager#default")
public class DefaultFileLockManager
implements FileLockManager
{
private static final ConcurrentMap<File, Lock> lockFiles = new ConcurrentHashMap<File, Lock>( 64 );
private boolean skipLocking = false;
private Logger log = LoggerFactory.getLogger( getClass() );
@Override
public Lock readFileLock( File file )
throws FileLockException
{
if ( skipLocking )
{
try
{
return new Lock( file, false );
}
catch ( IOException e )
{
throw new FileLockException( e.getMessage(), e );
}
}
Lock lock = lockFiles.get( file );
if ( lock == null )
{
try
{
lock = new Lock( file, false );
Lock current = lockFiles.putIfAbsent( file, lock );
if ( current != null )
{
lock = current;
}
return lock;
}
catch ( IOException e )
{
throw new FileLockException( e.getMessage(), e );
}
catch ( OverlappingFileLockException e )
{
log.debug( "OverlappingFileLockException: {}", e.getMessage() );
if ( lock == null )
{
lock = lockFiles.get( file );
}
}
}
// FIXME add a timeout on getting that!!!
while ( true )
{
log.debug( "wait read lock" );
synchronized ( lock )
{
if ( lock.getFileLock().isShared() || !lock.getFileLock().isValid() )
{
lock.addFileClient( Thread.currentThread() );
return lock;
}
}
}
//return lock;
}
@Override
public Lock writeFileLock( File file )
throws FileLockException
{
try
{
if ( skipLocking )
{
return new Lock( file, true );
}
// FIXME add a timeout on getting that!!!
while ( true )
{
Lock lock = lockFiles.get( file );
log.debug( "wait write lock" );
if ( lock != null )
{
synchronized ( lock )
{
if ( lock.getFileLock().isValid() || lock.getFileClients().size() > 0 )
{
continue;
}
return lock;
}
}
else
{
try
{
lock = new Lock( file, true );
}
catch ( OverlappingFileLockException e )
{
log.debug( "OverlappingFileLockException: {}", e.getMessage() );
if ( lock == null )
{
lock = lockFiles.get( file );
}
lock = lockFiles.get( file );
log.debug( "OverlappingFileLockException get: {}", lock );
}
Lock current = lockFiles.putIfAbsent( file, lock );
if ( current != null )
{
lock = current;
}
return lock;
}
}
}
catch ( IOException e )
{
throw new FileLockException( e.getMessage(), e );
}
}
@Override
public void release( Lock lock )
throws FileLockException
{
if ( lock == null )
{
log.debug( "skip releasing null" );
return;
}
if ( skipLocking )
{
return;
}
try
{
if ( lock.isWrite().get() )
{
lock.getFileLock().release();
}
synchronized ( lock )
{
lock.close();
if ( lock.getFileClients().size() < 1 )
{
lockFiles.remove( lock.getFile() );
}
}
}
catch ( IOException e )
{
throw new FileLockException( e.getMessage(), e );
}
}
}

View File

@ -0,0 +1,13 @@
package org.apache.archiva.common.filelock;
/**
* @author Olivier Lamy
*/
public class FileLockException
extends Exception
{
public FileLockException( String s, Throwable throwable )
{
super( s, throwable );
}
}

View File

@ -0,0 +1,18 @@
package org.apache.archiva.common.filelock;
import java.io.File;
/**
* @author Olivier Lamy
*/
public interface FileLockManager
{
Lock writeFileLock( File file )
throws FileLockException;
Lock readFileLock( File file )
throws FileLockException;
void release( Lock lock )
throws FileLockException;
}

View File

@ -0,0 +1,105 @@
package org.apache.archiva.common.filelock;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileLock;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author Olivier Lamy
*/
public class Lock
{
private File file;
private AtomicBoolean write;
private final Map<Thread, AtomicInteger> fileClients = new HashMap<Thread, AtomicInteger>();
private FileLock fileLock;
public Lock( File file, boolean write )
throws FileNotFoundException, IOException
{
this.file = file;
this.write = new AtomicBoolean( write );
this.openLock( write );
}
public File getFile()
{
return file;
}
public AtomicBoolean isWrite()
{
return write;
}
public void setFile( File file )
{
this.file = file;
}
public void setWrite( boolean write )
{
this.write.set( write );
}
public FileLock getFileLock()
{
return fileLock;
}
public void setFileLock( FileLock fileLock )
{
this.fileLock = fileLock;
}
public Map<Thread, AtomicInteger> getFileClients()
{
return fileClients;
}
public void addFileClient( Thread thread )
{
this.fileClients.put( thread, new AtomicInteger( 1 ) );
}
public boolean removeFileClient( Thread thread )
{
return this.fileClients.remove( thread ) != null;
}
protected void close()
throws IOException
{
if ( this.write.get() )
{
this.fileLock.release();
fileClients.remove( Thread.currentThread() );
}
}
public void openLock( boolean write )
throws IOException
{
fileClients.put( Thread.currentThread(), new AtomicInteger( 1 ) );
RandomAccessFile raf = new RandomAccessFile( file, write ? "rw" : "r" );
this.fileLock = raf.getChannel().lock( 1, 1, !write );
}
@Override
public String toString()
{
final StringBuilder sb = new StringBuilder( "Lock{" );
sb.append( "file=" ).append( file );
sb.append( '}' );
return sb.toString();
}
}

View File

@ -0,0 +1,34 @@
<?xml version="1.0"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one
~ or more contributor license agreements. See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership. The ASF licenses this file
~ to you 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.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd"
default-lazy-init="true">
<context:annotation-config/>
<context:component-scan base-package="org.apache.archiva.common.filelock"/>
</beans>

View File

@ -0,0 +1,208 @@
package org.apache.archiva.common.filelock;
import edu.umd.cs.mtc.MultithreadedTestCase;
import edu.umd.cs.mtc.TestFramework;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @author Olivier Lamy
*/
@RunWith( SpringJUnit4ClassRunner.class )
@ContextConfiguration( locations = { "classpath*:/META-INF/spring-context.xml" } )
public class DefaultFileLockManagerTest
{
final Logger logger = LoggerFactory.getLogger( getClass() );
@Inject
@Named( value = "fileLockManager#default" )
FileLockManager fileLockManager;
class ConcurentFileWrite
extends MultithreadedTestCase
{
FileLockManager fileLockManager;
File file = new File( System.getProperty( "buildDirectory" ), "foo.txt" );
File largeJar = new File( System.getProperty( "basedir" ), "src/test/cassandra-all-2.0.3.jar" );
ConcurentFileWrite( FileLockManager fileLockManager )
throws IOException
{
this.fileLockManager = fileLockManager;
file.createNewFile();
}
@Override
public void initialize()
{
}
public void thread1()
throws FileLockException, IOException
{
logger.info( "thread1" );
Lock lock = fileLockManager.writeFileLock( this.file );
try
{
lock.getFile().delete();
FileUtils.copyFile( largeJar, lock.getFile() );
}
finally
{
fileLockManager.release( lock );
}
logger.info( "thread1 ok" );
}
public void thread2()
throws FileLockException, IOException
{
logger.info( "thread2" );
Lock lock = fileLockManager.writeFileLock( this.file );
try
{
lock.getFile().delete();
FileUtils.copyFile( largeJar, lock.getFile() );
}
finally
{
fileLockManager.release( lock );
}
logger.info( "thread2 ok" );
}
public void thread3()
throws FileLockException, IOException
{
logger.info( "thread3" );
Lock lock = fileLockManager.readFileLock( this.file );
try
{
IOUtils.copy( new FileInputStream( lock.getFile() ),
new FileOutputStream( File.createTempFile( "foo", ".jar" ) ) );
}
finally
{
fileLockManager.release( lock );
}
logger.info( "thread3 ok" );
}
public void thread4()
throws FileLockException, IOException
{
logger.info( "thread4" );
Lock lock = fileLockManager.writeFileLock( this.file );
try
{
lock.getFile().delete();
FileUtils.copyFile( largeJar, lock.getFile() );
}
finally
{
fileLockManager.release( lock );
}
logger.info( "thread4 ok" );
}
public void thread5()
throws FileLockException, IOException
{
logger.info( "thread5" );
Lock lock = fileLockManager.writeFileLock( this.file );
try
{
lock.getFile().delete();
FileUtils.copyFile( largeJar, lock.getFile() );
}
finally
{
fileLockManager.release( lock );
}
logger.info( "thread5 ok" );
}
public void thread6()
throws FileLockException, IOException
{
logger.info( "thread6" );
Lock lock = fileLockManager.readFileLock( this.file );
try
{
IOUtils.copy( new FileInputStream( lock.getFile() ),
new FileOutputStream( File.createTempFile( "foo", ".jar" ) ) );
}
finally
{
fileLockManager.release( lock );
}
logger.info( "thread6 ok" );
}
public void thread7()
throws FileLockException, IOException
{
logger.info( "thread7" );
Lock lock = fileLockManager.writeFileLock( this.file );
try
{
lock.getFile().delete();
FileUtils.copyFile( largeJar, lock.getFile() );
}
finally
{
fileLockManager.release( lock );
}
logger.info( "thread7 ok" );
}
public void thread8()
throws FileLockException, IOException
{
logger.info( "thread8" );
Lock lock = fileLockManager.readFileLock( this.file );
try
{
IOUtils.copy( new FileInputStream( lock.getFile() ),
new FileOutputStream( File.createTempFile( "foo", ".jar" ) ) );
}
finally
{
fileLockManager.release( lock );
}
logger.info( "thread8 ok" );
}
}
@Test
public void testWrite()
throws Throwable
{
ConcurentFileWrite concurentFileWrite = new ConcurentFileWrite( fileLockManager );
//concurentFileWrite.setTrace( true );
TestFramework.runOnce( concurentFileWrite );
}
}

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one
~ or more contributor license agreements. See the NOTICE file
~ distributed with this work for additional information
~ regarding copyright ownership. The ASF licenses this file
~ to you 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.
-->
<configuration status="debug">
<appenders>
<Console name="console" target="SYSTEM_OUT">
<!--PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/-->
<PatternLayout pattern="%highlight{%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n}" />
</Console>
</appenders>
<loggers>
<logger name="org.apache.archiva.common.filelock" level="debug"/>
<root level="info" includeLocation="true">
<appender-ref ref="console"/>
</root>
</loggers>
</configuration>

View File

@ -83,6 +83,7 @@ import javax.inject.Inject;
import javax.inject.Named;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -1091,6 +1092,9 @@ public class DefaultRepositoryProxyConnectors
}
target.getParentFile().mkdirs();
// TODO file lock library
RandomAccessFile raf;
if ( !temp.renameTo( target ) )
{
log.warn( "Unable to rename tmp file to its final name... resorting to copy command." );

View File

@ -32,6 +32,7 @@
<modules>
<module>archiva-test-utils</module>
<module>archiva-common</module>
<module>archiva-filelock</module>
<module>archiva-model</module>
<module>archiva-configuration</module>
<module>archiva-checksum</module>

View File

@ -201,6 +201,7 @@ public class ArchivaDavResource
FileInputStream is = null;
try
{
// TODO file lock library
// Write content to stream
is = new FileInputStream( localResource );
IOUtils.copy( is, outputContext.getOutputStream() );

View File

@ -260,6 +260,11 @@
<artifactId>archiva-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.archiva</groupId>
<artifactId>archiva-filelock</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.archiva</groupId>
<artifactId>archiva-configuration</artifactId>