/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.datamanagement.backend.data.efs.internal;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.runtime.CoreException;
import org.osgi.framework.BundleContext;
import de.rcenvironment.core.configuration.ConfigurationService;
import de.rcenvironment.core.configuration.ConfigurationService.ConfigurablePathId;
import de.rcenvironment.core.datamanagement.backend.DataBackend;
/**
* IEFS implementation of {@link DataBackend}.
*
* @author Sandra Schroedter
* @author Juergen Klein
* @author Sascha Zur
* @author Robert Mischke (added stream buffering)
*/
public class EFSDataBackend implements DataBackend {
/** Supported scheme. */
public static final String SCHEMA_EFS = "efs";
private static final String SCHEME_URI_COMPLETION = "://";
private static final String SLASH = "/";
private static final String ZIP_FILE_SUFFIX = ".gz";
private static final String STORAGE_SUBDIRECTORY = "data";
private static final String FAILED_TO_WRITE_FILE_FOR_URI = "Failed to write file for URI ";
private static final int STREAM_BUFFER_SIZE = 256 * 1024; // arbitrary
private static Pattern parentPattern;
private static Pattern uriPattern;
private static boolean useGZipCompression = false;
private EFSDataBackendConfiguration configuration;
private ConfigurationService configurationService;
private EncapsulatedEFSService encapsulatedEFSService;
private final Log log = LogFactory.getLog(getClass());
static {
/**
* String defining the pattern for the parent directory.
*/
final String patternStrParent = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
/**
* String defining the pattern for an URI.
*/
final String patternStrURI = SLASH + patternStrParent;
/**
* Compiled pattern for the parent directory.
*/
parentPattern = Pattern.compile(patternStrParent);
/**
* Compiled pattern for an URI.
*/
uriPattern = Pattern.compile(patternStrURI);
}
protected void activate(BundleContext context) {
// note: disabled old configuration loading for 6.0.0 as the only parameter is not being used anymore anyway
// configuration = configurationService.getConfiguration(context.getBundle().getSymbolicName(), EFSDataBackendConfiguration.class);
// TODO use default configuration until reworked or removed
configuration = new EFSDataBackendConfiguration();
useGZipCompression = true; // TODO always on since 6.0.0; remove parameter?
if (configuration.getEfsStorage().equals("")) {
File efsSubDir = configurationService.initializeSubDirInConfigurablePath(ConfigurablePathId.PROFILE_DATA_MANAGEMENT,
STORAGE_SUBDIRECTORY);
String efsPath = efsSubDir.getAbsolutePath();
configuration.setEfsStorage(efsPath);
log.debug("Initializing EFS storage in " + efsPath);
} else {
// note: the "else" path was not doing anything before; at least log if this happens
log.warn("Unexpected state: EFS storage path already defined");
}
}
protected void bindConfigurationService(ConfigurationService newConfigurationService) {
configurationService = newConfigurationService;
}
protected void bindEncapsulatedEFSService(EncapsulatedEFSService newEncapsulatedEFService) {
encapsulatedEFSService = newEncapsulatedEFService;
}
@Override
public boolean delete(URI uri) {
boolean deleted = false;
if (!isURIValid(uri)) {
throw new IllegalArgumentException("Given URI representing a file to delete is not valid: " + uri);
}
try {
File fileToDelete = new File(getFileStorageRoot().getAbsolutePath() + new File(uri.getRawPath()).getPath());
if (!fileToDelete.exists()) {
fileToDelete = new File(getFileStorageRoot().getAbsolutePath() + new File(uri.getRawPath()).getPath() + ZIP_FILE_SUFFIX);
}
if (!fileToDelete.exists()) {
log.debug("Given URI representing a file to delete could not be resolved to an existing path in the file store: "
+ fileToDelete.getAbsolutePath());
} else {
IFileStore fileStore = encapsulatedEFSService.getStore(fileToDelete.toURI());
fileStore.delete(EFS.NONE, null);
deleted = true;
// remove file store parent if there are no more revisions stored
IFileStore parent = fileStore.getParent();
if (isParentValid(parent.getName())) {
if (parent.childNames(EFS.NONE, null).length == 0) {
parent.delete(EFS.NONE, null);
}
}
}
} catch (CoreException e) {
// CoreException not wrapped in RuntimeException as it contains a
// non-serializable object: org.eclipse.core.runtime.MultiStatus and
// as this method is accessible from remote
throw new RuntimeException("File with given URI could not be deleted: " + uri
+ " (Message: " + e.getMessage() + ")", e.getCause());
}
return deleted;
}
@Override
public InputStream get(URI uri) {
IFileStore fileStore = null;
if (!isURIValid(uri)) {
throw new IllegalArgumentException("Given URI representing a file to get is not valid: " + uri);
}
try {
boolean isZipped = false;
File fileToGet = new File(getFileStorageRoot().getAbsolutePath() + new File(uri.getRawPath()).getPath());
if (!fileToGet.exists()) {
fileToGet = new File(getFileStorageRoot().getAbsolutePath() + new File(uri.getRawPath()).getPath() + ZIP_FILE_SUFFIX);
isZipped = true;
}
fileStore = encapsulatedEFSService.getStore(fileToGet.toURI());
InputStream storageInputStream;
// get buffered storage file stream
if (isZipped) {
storageInputStream =
new BufferedInputStream(new GzipCompressorInputStream(fileStore.openInputStream(EFS.NONE, null)), STREAM_BUFFER_SIZE);
} else {
storageInputStream = new BufferedInputStream(fileStore.openInputStream(EFS.NONE, null), STREAM_BUFFER_SIZE);
}
return storageInputStream;
} catch (CoreException e) {
throw new RuntimeException("File with given URI could not be found: " + uri, e);
} catch (IOException e) {
throw new RuntimeException("File with given URI could not be found: " + uri, e);
}
}
@Override
public URI suggestLocation(UUID guid) {
URI newUri;
try {
newUri = new URI(SCHEMA_EFS + SCHEME_URI_COMPLETION + SLASH + guid.toString());
} catch (URISyntaxException e) {
// should never get here
throw new IllegalArgumentException("Creating URI failed.", e);
}
return newUri;
}
@Override
// TODO messy exception handling; improve
public long put(URI uri, Object object) {
if (!isURIValid(uri)) {
throw new IllegalArgumentException("Given URI representing the location to put a file to is not valid: " + uri);
}
long writtenBytes = 0;
if (object instanceof InputStream) {
InputStream inputStream = (InputStream) object;
OutputStream storageOutputStream = null;
IFileStore fileStore = null;
try {
File fileToSave;
IFileStore parent = null;
if (useGZipCompression) {
fileToSave = new File(getFileStorageRoot().getAbsolutePath() + new File(uri.getRawPath()).getPath() + ZIP_FILE_SUFFIX);
} else {
fileToSave = new File(getFileStorageRoot().getAbsolutePath() + new File(uri.getRawPath()).getPath());
}
fileStore = encapsulatedEFSService.getStore(fileToSave.toURI());
parent = fileStore.getParent();
if (parent != null && isParentValid(parent.getName())) {
parent.mkdir(0, null);
}
// get buffered storage file stream for writing
if (useGZipCompression) {
storageOutputStream =
new BufferedOutputStream(new GzipCompressorOutputStream(fileStore.openOutputStream(EFS.NONE, null)),
STREAM_BUFFER_SIZE);
} else {
storageOutputStream = new BufferedOutputStream(fileStore.openOutputStream(EFS.NONE, null), STREAM_BUFFER_SIZE);
}
} catch (CoreException e) {
// TODO review: RTEs should only be thrown when unavoidable; change method API to
// declare explicit exceptions - misc_ro
throw new RuntimeException(FAILED_TO_WRITE_FILE_FOR_URI + uri, e);
} catch (IOException e) {
// TODO review: RTEs should only be thrown when unavoidable; change method API to
// declare explicit exceptions - misc_ro
throw new RuntimeException(FAILED_TO_WRITE_FILE_FOR_URI + uri, e);
}
try {
final int minusOne = -1;
final int bufferSize = 256 * 1024;
byte[] buffer = new byte[bufferSize];
int n = 0;
while (minusOne != (n = inputStream.read(buffer))) {
storageOutputStream.write(buffer, 0, n);
writtenBytes += n;
}
} catch (IOException e) {
try {
fileStore.delete(EFS.NONE, null);
IFileStore parent = fileStore.getParent();
if (isParentValid(parent.getName())) {
// delete directory if it is empty
if (parent.childNames(EFS.NONE, null).length == 0) {
parent.delete(EFS.NONE, null);
}
}
} catch (CoreException e2) {
log.error("File with given URI for which writing failed could not be deleted: " + uri);
}
throw new RuntimeException(FAILED_TO_WRITE_FILE_FOR_URI + uri, e);
} finally {
try {
storageOutputStream.close();
} catch (IOException e2) {
log.error("EFS output stream for given URI could not be closed: " + uri, e2);
}
try {
inputStream.close();
} catch (IOException e2) {
log.error("Input stream for given URI could not be closed: " + uri, e2);
}
}
} else {
throw new IllegalArgumentException("Given object to put is not an instance of InputStream: " + object);
}
return writtenBytes;
}
/**
* Checks if the given name resembles a valid URI for the parent directory of a persisted file.
*
* @param name Name to be checked.
* @return <code>true</code> if name resembles a valid parent directory, <code>false</code> otherwise.
*/
private boolean isParentValid(String name) {
// must be like f8f3fe28-7970-4f5d-a7ea-8f611f6aa83c
String stringToTest = name;
Matcher matcher = parentPattern.matcher(stringToTest);
boolean isValue = matcher.matches();
return isValue;
}
/**
* Checks if the given URI resembles a valid URI for the persisted file.
*
* @param uri URI to be checked.
* @return <code>true</code> if URI is valid, <code>false</code> otherwise.
*/
private boolean isURIValid(URI uri) {
// must be like /f8f3fe28-7970-4f5d-a7ea-8f611f6aa83c/file-1
String stringToTest = uri.getPath();
Matcher matcher = uriPattern.matcher(stringToTest);
boolean isValue = matcher.matches();
return isValue;
}
private File getFileStorageRoot() {
return new File(configuration.getEfsStorage()).getAbsoluteFile();
}
}