/*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
*
* 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.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.uberfire.commons.lifecycle.PriorityDisposableRegistry;
import org.uberfire.io.IOWatchService;
import org.uberfire.io.lock.BatchLockControl;
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.commons.validation.PortablePreconditions.checkNotNull;
import static org.uberfire.java.nio.file.StandardOpenOption.CREATE_NEW;
import static org.uberfire.java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static org.uberfire.java.nio.file.StandardOpenOption.WRITE;
public abstract class AbstractIOService implements IOServiceIdentifiable,
IOServiceLockable {
protected static final String DEFAULT_SERVICE_NAME = "default";
protected static final Charset UTF_8 = Charset.forName("UTF-8");
private static final Logger logger = LoggerFactory.getLogger(AbstractIOService.class);
private static final Set<StandardOpenOption> CREATE_NEW_FILE_OPTIONS = EnumSet.of(CREATE_NEW,
WRITE);
protected final IOWatchService ioWatchService;
protected final Set<FileSystem> fileSystems = Collections.newSetFromMap(new ConcurrentHashMap<FileSystem, Boolean>());
private final BatchLockControl batchLockControl = new BatchLockControl();
protected NewFileSystemListener newFileSystemListener = null;
protected boolean isDisposed = false;
private String id;
public AbstractIOService() {
this.id = DEFAULT_SERVICE_NAME;
ioWatchService = null;
PriorityDisposableRegistry.register(this);
}
public AbstractIOService(final String id) {
this.id = id;
ioWatchService = null;
PriorityDisposableRegistry.register(this);
}
public AbstractIOService(final IOWatchService watchService) {
this.id = DEFAULT_SERVICE_NAME;
ioWatchService = watchService;
PriorityDisposableRegistry.register(this);
}
public AbstractIOService(final String id,
final IOWatchService watchService) {
this.id = id;
ioWatchService = watchService;
PriorityDisposableRegistry.register(this);
}
@Override
public void startBatch(FileSystem fs) {
batchProcess(new FileSystem[]{fs});
}
@Override
public void startBatch(FileSystem fs,
final Option... options) {
batchProcess(new FileSystem[]{fs},
options);
}
@Override
public void startBatch(final FileSystem... fs) {
batchProcess(fs);
}
@Override
public void startBatch(FileSystem[] fs,
final Option... options) {
batchProcess(fs,
options);
}
private void batchProcess(final FileSystem[] fs,
final 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(getFirstRootDirectory(fs),
FileSystemState.FILE_SYSTEM_STATE_ATTR,
options[0]);
}
}
}
private void startBatchProcess(final FileSystem... fileSystems) {
batchLockControl.lock(fileSystems);
for (final FileSystem fs : fileSystems) {
setBatchModeOn(fs);
}
}
@Override
public void endBatch() {
if (!batchLockControl.isLocked()) {
throw new RuntimeException("There is no batch process.");
}
if (batchLockControl.getHoldCount() > 1) {
batchLockControl.unlock();
return;
}
try {
cleanUpAndUnsetBatchModeOnFileSystems();
} catch (Exception e) {
throw new RuntimeException("Exception cleaning and unsetting batch mode on FS.",
e);
} finally {
batchLockControl.unlock();
}
}
private void cleanUpAndUnsetBatchModeOnFileSystems() {
if (!fileSystems.isEmpty()) {
cleanupClosedFileSystems();
}
for (final FileSystem fs : fileSystems) {
unsetBatchModeOn(fs);
}
}
@Override
public BatchLockControl getLockControl() {
return batchLockControl;
}
private void cleanupClosedFileSystems() {
final ArrayList<FileSystem> removeList = new ArrayList<FileSystem>();
for (final FileSystem fileSystem : fileSystems) {
if (!fileSystem.isOpen()) {
removeList.add(fileSystem);
}
}
fileSystems.removeAll(removeList);
}
private void setBatchModeOn(FileSystem fs) {
Files.setAttribute(getFirstRootDirectory(fs),
FileSystemState.FILE_SYSTEM_STATE_ATTR,
FileSystemState.BATCH);
}
private Path getFirstRootDirectory(FileSystem fs) {
checkNotNull("fs",
fs);
Iterable<Path> rootDirectories = checkNotNull("fs.getRootDirectories()",
fs.getRootDirectories());
Iterator<Path> iterator = checkNotNull("fs.getRootDirectories().iterator()",
rootDirectories.iterator());
return iterator.next();
}
void unsetBatchModeOn(FileSystem fs) {
Files.setAttribute(getFirstRootDirectory(fs),
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.error("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());
}
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 {
return Files.newOutputStream(path,
options);
}
@Override
public SeekableByteChannel newByteChannel(final Path path,
final OpenOption... options)
throws IllegalArgumentException, UnsupportedOperationException,
FileAlreadyExistsException, IOException, SecurityException {
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 {
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 Path createFile(final Path path,
final FileAttribute<?>... attrs)
throws IllegalArgumentException, UnsupportedOperationException, FileAlreadyExistsException,
IOException, SecurityException {
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 {
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 {
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 {
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 Path write(final Path path,
final byte[] bytes,
final Set<? extends OpenOption> options,
final FileAttribute<?>... attrs) throws IllegalArgumentException, IOException, UnsupportedOperationException {
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;
}
}