/*
* Copyright 2012 JBoss Inc
*
* 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.uberfire.io.impl;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.uberfire.io.IOWatchService;
import org.uberfire.io.lock.BatchLockControl;
import org.uberfire.io.lock.FSLockService;
import org.uberfire.io.lock.impl.BatchLockControlImpl;
import org.uberfire.io.lock.impl.FSLockServiceImpl;
import org.uberfire.java.nio.IOException;
import org.uberfire.java.nio.base.AbstractPath;
import org.uberfire.java.nio.base.FileSystemState;
import org.uberfire.java.nio.channels.SeekableByteChannel;
import org.uberfire.java.nio.file.CopyOption;
import org.uberfire.java.nio.file.DirectoryNotEmptyException;
import org.uberfire.java.nio.file.DirectoryStream;
import org.uberfire.java.nio.file.FileAlreadyExistsException;
import org.uberfire.java.nio.file.FileSystem;
import org.uberfire.java.nio.file.FileSystemAlreadyExistsException;
import org.uberfire.java.nio.file.FileSystemNotFoundException;
import org.uberfire.java.nio.file.FileSystems;
import org.uberfire.java.nio.file.Files;
import org.uberfire.java.nio.file.NoSuchFileException;
import org.uberfire.java.nio.file.NotDirectoryException;
import org.uberfire.java.nio.file.OpenOption;
import org.uberfire.java.nio.file.Option;
import org.uberfire.java.nio.file.Path;
import org.uberfire.java.nio.file.Paths;
import org.uberfire.java.nio.file.ProviderNotFoundException;
import org.uberfire.java.nio.file.StandardOpenOption;
import org.uberfire.java.nio.file.attribute.FileAttribute;
import org.uberfire.java.nio.file.attribute.FileTime;
import static org.uberfire.java.nio.file.StandardOpenOption.*;
public abstract class AbstractIOService implements IOServiceIdentifiable {
private static final Logger logger = LoggerFactory.getLogger( AbstractIOService.class );
protected static final String DEFAULT_SERVICE_NAME = "default";
private static final Set<StandardOpenOption> CREATE_NEW_FILE_OPTIONS = EnumSet.of( CREATE_NEW, WRITE );
protected static final Charset UTF_8 = Charset.forName( "UTF-8" );
protected final FSLockService lockService;
protected final BatchLockControl batchLockControl;
protected final IOWatchService ioWatchService;
protected final Set<FileSystem> fileSystems = new HashSet<FileSystem>();
protected NewFileSystemListener newFileSystemListener = null;
protected boolean isDisposed = false;
private String id;
public AbstractIOService() {
this.id = DEFAULT_SERVICE_NAME;
lockService = new FSLockServiceImpl();
batchLockControl = new BatchLockControlImpl();
ioWatchService = null;
}
public AbstractIOService( final String id ) {
this.id = id;
lockService = new FSLockServiceImpl();
batchLockControl = new BatchLockControlImpl();
ioWatchService = null;
}
public AbstractIOService( final IOWatchService watchService ) {
this.id = DEFAULT_SERVICE_NAME;
lockService = new FSLockServiceImpl();
batchLockControl = new BatchLockControlImpl();
ioWatchService = watchService;
}
public AbstractIOService( final String id,
final IOWatchService watchService ) {
this.id = id;
lockService = new FSLockServiceImpl();
batchLockControl = new BatchLockControlImpl();
ioWatchService = watchService;
}
public AbstractIOService( final FSLockService lockService,
final IOWatchService watchService ) {
this.id = DEFAULT_SERVICE_NAME;
this.lockService = lockService;
this.batchLockControl = new BatchLockControlImpl();
this.ioWatchService = watchService;
}
public AbstractIOService( final String id,
final FSLockService lockService,
final IOWatchService watchService ) {
this.id = id;
this.lockService = lockService;
this.ioWatchService = watchService;
this.batchLockControl = new BatchLockControlImpl();
}
@Override
public void startBatch( FileSystem fs,
final Option... options ) throws InterruptedException {
batchProcess( new FileSystem[]{ fs }, options );
}
@Override
public void startBatch( final FileSystem... fs ) throws InterruptedException {
batchProcess( fs );
}
@Override
public void startBatch( FileSystem[] fs,
final Option... options ) throws InterruptedException {
batchProcess( fs, options );
}
private void batchProcess( FileSystem[] fs,
Option... options ) {
startBatchProcess( fs );
if ( !fileSystems.isEmpty() ) {
cleanupClosedFileSystems();
setOptionsOnFileSystems( fs, options );
}
}
private void setOptionsOnFileSystems( FileSystem[] fss,
Option[] options ) {
if ( options != null && options.length == 1 ) {
for ( FileSystem fs : fss ) {
setAttribute( fs.getRootDirectories().iterator().next(), FileSystemState.FILE_SYSTEM_STATE_ATTR, options[ 0 ] );
}
}
}
@Override
public void endBatch() {
FileSystem[] lockedFileSystems = batchLockControl.getLockedFileSystems();
if ( lockedFileSystems == null || lockedFileSystems.length == 0 ) {
throw new RuntimeException( "There is no locked FS" );
}
for ( FileSystem fs : lockedFileSystems ) {
lockService.unlock( fs );
unsetBatchModeOn( fs );
}
if ( !fileSystems.isEmpty() ) {
cleanupClosedFileSystems();
}
batchLockControl.end();
}
private void startBatchProcess( FileSystem[] fileSystems ) {
batchLockControl.start( fileSystems );
sortFileSystemsForLocking( fileSystems );
for ( FileSystem fs : fileSystems ) {
setBatchModeOn( fs );
lockService.lock( fs );
}
}
private void sortFileSystemsForLocking( FileSystem[] fileSystems ) {
Arrays.sort( fileSystems, new Comparator<FileSystem>() {
@Override
public int compare( FileSystem o1,
FileSystem o2 ) {
Integer idO1 = System.identityHashCode( o1 );
Integer idO2 = System.identityHashCode( o2 );
return idO1.compareTo( idO2 );
}
} );
}
private synchronized void cleanupClosedFileSystems() {
final ArrayList<FileSystem> removeList = new ArrayList<FileSystem>();
for ( final FileSystem fileSystem : fileSystems ) {
if ( !fileSystem.isOpen() ) {
removeList.add( fileSystem );
lockService.removeFromService( fileSystem );
}
}
fileSystems.removeAll( removeList );
}
private synchronized void removeClosedFileSystems() {
final ArrayList<FileSystem> removeList = new ArrayList<FileSystem>();
for ( final FileSystem fileSystem : fileSystems ) {
if ( !fileSystem.isOpen() ) {
removeList.add( fileSystem );
lockService.removeFromService( fileSystem );
}
}
fileSystems.removeAll( removeList );
}
private void setBatchModeOn( FileSystem fs ) {
setAttribute( fs.getRootDirectories().iterator().next(), FileSystemState.FILE_SYSTEM_STATE_ATTR, FileSystemState.BATCH );
}
private void unsetBatchModeOn( FileSystem fs ) {
setAttribute( fs.getRootDirectories().iterator().next(), FileSystemState.FILE_SYSTEM_STATE_ATTR, FileSystemState.NORMAL );
}
@Override
public Path get( final String first,
final String... more ) throws IllegalArgumentException {
return Paths.get( first, more );
}
@Override
public Path get( final URI uri )
throws IllegalArgumentException, FileSystemNotFoundException, SecurityException {
return Paths.get( uri );
}
@Override
public Iterable<FileSystem> getFileSystems() {
return fileSystems;
}
@Override
public FileSystem getFileSystem( final URI uri ) {
try {
return registerFS( FileSystems.getFileSystem( uri ) );
} catch ( final Exception ex ) {
logger.warn( "Failed to register filesystem " + uri + " with DEFAULT_FS_TYPE. Returning null.", ex );
return null;
}
}
@Override
public FileSystem newFileSystem( final URI uri,
final Map<String, ?> env ) throws IllegalArgumentException, FileSystemAlreadyExistsException, ProviderNotFoundException, IOException, SecurityException {
try {
final FileSystem fs = FileSystems.newFileSystem( uri, env );
return registerFS( fs );
} catch ( final FileSystemAlreadyExistsException ex ) {
registerFS( FileSystems.getFileSystem( uri ) );
throw ex;
}
}
@Override
public void onNewFileSystem( final NewFileSystemListener listener ) {
this.newFileSystemListener = listener;
}
private FileSystem registerFS( final FileSystem fs ) {
if ( fs == null ) {
return fs;
}
if ( ioWatchService != null && !ioWatchService.hasWatchService( fs ) ) {
ioWatchService.addWatchService( fs, fs.newWatchService() );
}
synchronized ( this ) {
fileSystems.add( fs );
}
return fs;
}
@Override
public InputStream newInputStream( final Path path,
final OpenOption... options )
throws IllegalArgumentException, NoSuchFileException, UnsupportedOperationException,
IOException, SecurityException {
return Files.newInputStream( path, options );
}
@Override
public DirectoryStream<Path> newDirectoryStream( final Path dir )
throws IllegalArgumentException, NotDirectoryException, IOException, SecurityException {
return Files.newDirectoryStream( dir );
}
@Override
public DirectoryStream<Path> newDirectoryStream( final Path dir,
final DirectoryStream.Filter<Path> filter )
throws IllegalArgumentException, NotDirectoryException, IOException, SecurityException {
return Files.newDirectoryStream( dir, filter );
}
@Override
public OutputStream newOutputStream( final Path path,
final OpenOption... options )
throws IllegalArgumentException, UnsupportedOperationException,
IOException, SecurityException {
waitFSUnlock( path );
return Files.newOutputStream( path, options );
}
@Override
public SeekableByteChannel newByteChannel( final Path path,
final OpenOption... options )
throws IllegalArgumentException, UnsupportedOperationException,
FileAlreadyExistsException, IOException, SecurityException {
waitFSUnlock( path );
return Files.newByteChannel( path, options );
}
@Override
public Path createDirectory( final Path dir,
final Map<String, ?> attrs ) throws IllegalArgumentException, UnsupportedOperationException, FileAlreadyExistsException, IOException, SecurityException {
return createDirectory( dir, convert( attrs ) );
}
@Override
public Path createDirectories( final Path dir,
final Map<String, ?> attrs ) throws UnsupportedOperationException, FileAlreadyExistsException, IOException, SecurityException {
return createDirectories( dir, convert( attrs ) );
}
@Override
public Path createTempFile( final String prefix,
final String suffix,
final FileAttribute<?>... attrs )
throws IllegalArgumentException, UnsupportedOperationException, IOException, SecurityException {
return Files.createTempFile( prefix, suffix, attrs );
}
@Override
public Path createTempFile( final Path dir,
final String prefix,
final String suffix,
final FileAttribute<?>... attrs )
throws IllegalArgumentException, UnsupportedOperationException, IOException, SecurityException {
return Files.createTempFile( dir, prefix, suffix, attrs );
}
@Override
public Path createTempDirectory( final String prefix,
final FileAttribute<?>... attrs )
throws IllegalArgumentException, UnsupportedOperationException, IOException, SecurityException {
return Files.createTempDirectory( prefix, attrs );
}
@Override
public Path createTempDirectory( final Path dir,
final String prefix,
final FileAttribute<?>... attrs )
throws IllegalArgumentException, UnsupportedOperationException, IOException, SecurityException {
return Files.createTempDirectory( dir, prefix, attrs );
}
@Override
public FileTime getLastModifiedTime( final Path path )
throws IllegalArgumentException, IOException, SecurityException {
return Files.getLastModifiedTime( path );
}
@Override
public Map<String, Object> readAttributes( final Path path )
throws UnsupportedOperationException, NoSuchFileException, IllegalArgumentException,
IOException, SecurityException {
return readAttributes( path, "*" );
}
@Override
public Path setAttribute( final Path path,
final String attribute,
final Object value )
throws UnsupportedOperationException, IllegalArgumentException, ClassCastException, IOException, SecurityException {
waitFSUnlock( path );
Files.setAttribute( path, attribute, value );
return path;
}
@Override
public Path setAttributes( final Path path,
final Map<String, Object> attrs )
throws UnsupportedOperationException, IllegalArgumentException,
ClassCastException, IOException, SecurityException {
return setAttributes( path, convert( attrs ) );
}
@Override
public long size( final Path path )
throws IllegalArgumentException, IOException, SecurityException {
return Files.size( path );
}
@Override
public boolean exists( final Path path )
throws IllegalArgumentException, SecurityException {
return Files.exists( path );
}
@Override
public boolean notExists( final Path path )
throws IllegalArgumentException, SecurityException {
return Files.notExists( path );
}
@Override
public boolean isSameFile( final Path path,
final Path path2 )
throws IllegalArgumentException, IOException, SecurityException {
return Files.isSameFile( path, path2 );
}
@Override
public synchronized Path createFile( final Path path,
final FileAttribute<?>... attrs )
throws IllegalArgumentException, UnsupportedOperationException, FileAlreadyExistsException,
IOException, SecurityException {
waitFSUnlock( path );
try {
newByteChannel( path, CREATE_NEW_FILE_OPTIONS, attrs ).close();
} catch ( java.io.IOException e ) {
throw new IOException( e );
}
return path;
}
@Override
public BufferedReader newBufferedReader( final Path path,
final Charset cs )
throws IllegalArgumentException, NoSuchFileException, IOException, SecurityException {
return Files.newBufferedReader( path, cs );
}
@Override
public long copy( final Path source,
final OutputStream out )
throws IOException, SecurityException {
waitFSUnlock( source );
return Files.copy( source, out );
}
@Override
public byte[] readAllBytes( final Path path )
throws IOException, OutOfMemoryError, SecurityException {
return Files.readAllBytes( path );
}
@Override
public List<String> readAllLines( final Path path )
throws IllegalArgumentException, NoSuchFileException, IOException, SecurityException {
return readAllLines( path, UTF_8 );
}
@Override
public List<String> readAllLines( final Path path,
final Charset cs )
throws IllegalArgumentException, NoSuchFileException, IOException, SecurityException {
return Files.readAllLines( path, cs );
}
@Override
public String readAllString( final Path path,
final Charset cs ) throws IllegalArgumentException, NoSuchFileException, IOException {
final byte[] result = Files.readAllBytes( path );
if ( result == null && result.length < 0 ) {
return "";
}
return new String( result, cs );
}
@Override
public String readAllString( final Path path )
throws IllegalArgumentException, NoSuchFileException, IOException {
return readAllString( path, UTF_8 );
}
@Override
public BufferedWriter newBufferedWriter( final Path path,
final Charset cs,
final OpenOption... options )
throws IllegalArgumentException, IOException, UnsupportedOperationException, SecurityException {
waitFSUnlock( path );
return Files.newBufferedWriter( path, cs, options );
}
@Override
public long copy( final InputStream in,
final Path target,
final CopyOption... options )
throws IOException, FileAlreadyExistsException, DirectoryNotEmptyException, UnsupportedOperationException, SecurityException {
waitFSUnlock( target );
return Files.copy( in, target, options );
}
@Override
public Path write( final Path path,
final byte[] bytes,
final OpenOption... options )
throws IOException, UnsupportedOperationException, SecurityException {
return write( path, bytes, new HashSet<OpenOption>( Arrays.asList( options ) ) );
}
@Override
public Path write( final Path path,
final Iterable<? extends CharSequence> lines,
final Charset cs,
final OpenOption... options ) throws IllegalArgumentException, IOException, UnsupportedOperationException, SecurityException {
return write( path, toByteArray( lines, cs ), new HashSet<OpenOption>( Arrays.asList( options ) ) );
}
private byte[] toByteArray( final Iterable<? extends CharSequence> lines,
final Charset cs ) {
final StringBuilder sb = new StringBuilder();
for ( final CharSequence line : lines ) {
sb.append( line.toString() );
}
return sb.toString().getBytes();
}
@Override
public Path write( final Path path,
final String content,
final Charset cs,
final OpenOption... options )
throws IllegalArgumentException, IOException, UnsupportedOperationException {
return write( path, content.getBytes( cs ), new HashSet<OpenOption>( Arrays.asList( options ) ) );
}
@Override
public Path write( final Path path,
final String content,
final OpenOption... options )
throws IllegalArgumentException, IOException, UnsupportedOperationException {
return write( path, content, UTF_8, options );
}
@Override
public Path write( final Path path,
final String content,
final Map<String, ?> attrs,
final OpenOption... options )
throws IllegalArgumentException, IOException, UnsupportedOperationException {
return write( path, content, UTF_8, attrs, options );
}
@Override
public Path write( final Path path,
final String content,
final Charset cs,
final Map<String, ?> attrs,
final OpenOption... options )
throws IllegalArgumentException, IOException, UnsupportedOperationException {
return write( path, content, cs, new HashSet<OpenOption>( Arrays.asList( options ) ), convert( attrs ) );
}
@Override
public void dispose() {
isDisposed = true;
for ( final FileSystem fileSystem : getFileSystems() ) {
try {
fileSystem.dispose();
} catch ( final Exception ignored ) {
}
}
}
@Override
public FileAttribute<?>[] convert( final Map<String, ?> attrs ) {
if ( attrs == null || attrs.size() == 0 ) {
return new FileAttribute<?>[ 0 ];
}
final FileAttribute<?>[] attrsArray = new FileAttribute<?>[ attrs.size() ];
int i = 0;
for ( final Map.Entry<String, ?> attr : attrs.entrySet() ) {
attrsArray[ i++ ] = new FileAttribute<Object>() {
@Override
public String name() {
return attr.getKey();
}
@Override
public Object value() {
return attr.getValue();
}
};
}
return attrsArray;
}
@Override
public Path write( final Path path,
final byte[] bytes,
final Map<String, ?> attrs,
final OpenOption... options ) throws IOException, UnsupportedOperationException, SecurityException {
return write( path, bytes, new HashSet<OpenOption>( Arrays.asList( options ) ), convert( attrs ) );
}
@Override
public Path write( final Path path,
final String content,
final Set<? extends OpenOption> options,
final FileAttribute<?>... attrs )
throws IllegalArgumentException, IOException, UnsupportedOperationException {
return write( path, content, UTF_8, options, attrs );
}
@Override
public Path write( final Path path,
final String content,
final Charset cs,
final Set<? extends OpenOption> options,
final FileAttribute<?>... attrs )
throws IllegalArgumentException, IOException, UnsupportedOperationException {
return write( path, content.getBytes( cs ), options, attrs );
}
@Override
public synchronized Path write( final Path path,
final byte[] bytes,
final Set<? extends OpenOption> options,
final FileAttribute<?>... attrs ) throws IllegalArgumentException, IOException, UnsupportedOperationException {
waitFSUnlock( path );
SeekableByteChannel byteChannel;
try {
byteChannel = newByteChannel( path, buildOptions( options ), attrs );
} catch ( final FileAlreadyExistsException ex ) {
( (AbstractPath) path ).clearCache();
byteChannel = newByteChannel( path, buildOptions( options, TRUNCATE_EXISTING ), attrs );
}
try {
byteChannel.write( ByteBuffer.wrap( bytes ) );
byteChannel.close();
} catch ( final java.io.IOException e ) {
throw new IOException( e );
}
return path;
}
protected abstract Set<? extends OpenOption> buildOptions( final Set<? extends OpenOption> options,
final OpenOption... other );
@Override
public String getId() {
return id;
}
protected void waitFSUnlock( Path path ) {
if ( path != null && path.getFileSystem() != null ) {
lockService.waitForUnlock( path.getFileSystem() );
}
}
}