Merge branch 'release/1.1.4' into develop

This commit is contained in:
Sebastian Stenzel
2016-08-14 15:04:44 +02:00
22 changed files with 272 additions and 124 deletions

View File

@@ -9,7 +9,7 @@ Priority: optional
Architecture: APPLICATION_ARCH
Provides: APPLICATION_PACKAGE
Installed-Size: APPLICATION_INSTALLED_SIZE
Depends: gvfs-bin, gvfs-backends, gvfs-fuse, xdg-utils
Depends: gvfs-bin, gvfs-backends, gvfs-fuse
Description: Multi-platform client-side encryption of your cloud files.
Cryptomator provides free client-side AES encryption for your cloud files.
Create encrypted vaults, which get mounted as virtual volumes. Whatever

View File

@@ -85,11 +85,15 @@ final class ConflictResolver {
} else {
ByteBuffer beginOfFile1 = ByteBuffer.allocate(sampleSize);
ByteBuffer beginOfFile2 = ByteBuffer.allocate(sampleSize);
r1.read(beginOfFile1);
r2.read(beginOfFile2);
beginOfFile1.flip();
beginOfFile2.flip();
return beginOfFile1.equals(beginOfFile2);
int bytesRead1 = r1.read(beginOfFile1);
int bytesRead2 = r2.read(beginOfFile2);
if (bytesRead1 == bytesRead2) {
beginOfFile1.flip();
beginOfFile2.flip();
return beginOfFile1.equals(beginOfFile2);
} else {
return false;
}
}
}
}

View File

@@ -24,7 +24,10 @@ public interface Frontend extends AutoCloseable {
void mount(Map<MountParam, Optional<String>> map) throws CommandFailedException;
void unmount() throws CommandFailedException;
/**
* Unmounts the file system and stops any file system handler threads.
*/
void close() throws Exception;
void reveal() throws CommandFailedException;

View File

@@ -16,10 +16,11 @@ public interface FrontendFactory {
* Provides a new frontend to access the given folder.
*
* @param root Root resource accessible through this frontend.
* @param uniqueName Name of the frontend, i.e. used to create subresources for the different frontends inside of a common virtual drive.
* @param id unique id of the frontend, i.e. used to generate a unique uri
* @param name Name of the frontend, i.e. used to generate a readable/recognizable name of a common virtual drive
* @return A new frontend
* @throws FrontendCreationFailedException If creation was not possible.
*/
Frontend create(Folder root, String uniqueName) throws FrontendCreationFailedException;
Frontend create(Folder root, FrontendId id, String name) throws FrontendCreationFailedException;
}

View File

@@ -0,0 +1,85 @@
package org.cryptomator.frontend;
import static java.util.UUID.randomUUID;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.UUID;
public class FrontendId implements Serializable {
public static final String FRONTEND_ID_PATTERN = "[a-zA-Z0-9_-]{12}";
public static FrontendId generate() {
return new FrontendId();
}
public static FrontendId from(String value) {
return new FrontendId(value);
}
private final String value;
private FrontendId() {
this(generateId());
}
private FrontendId(String value) {
if (!value.matches(FRONTEND_ID_PATTERN)) {
throw new IllegalArgumentException("Invalid frontend id " + value);
}
this.value = value;
}
private static String generateId() {
return asBase64String(nineBytesFrom(randomUUID()));
}
private static String asBase64String(ByteBuffer bytes) {
ByteBuffer base64Buffer = Base64.getUrlEncoder().encode(bytes);
return new String(asByteArray(base64Buffer), StandardCharsets.US_ASCII);
}
private static ByteBuffer nineBytesFrom(UUID uuid) {
ByteBuffer uuidBuffer = ByteBuffer.allocate(9);
uuidBuffer.putLong(uuid.getMostSignificantBits());
uuidBuffer.put((byte) (uuid.getLeastSignificantBits() & 0xFF));
uuidBuffer.flip();
return uuidBuffer;
}
private static byte[] asByteArray(ByteBuffer buffer) {
if (buffer.hasArray()) {
return buffer.array();
} else {
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
return bytes;
}
}
@Override
public boolean equals(Object obj) {
if (obj == null || obj.getClass() != getClass()) {
return false;
}
return obj == this || internalEquals((FrontendId) obj);
}
private boolean internalEquals(FrontendId obj) {
return value.equals(obj.value);
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public String toString() {
return value;
}
}

View File

@@ -0,0 +1,30 @@
package org.cryptomator.frontend.webdav;
import static java.lang.String.format;
import static org.cryptomator.frontend.FrontendId.FRONTEND_ID_PATTERN;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.cryptomator.frontend.FrontendId;
class ContextPaths {
private static final Pattern SERVLET_PATH_WITH_FRONTEND_ID_PATTERN = Pattern.compile("^/(" + FRONTEND_ID_PATTERN + ")(/.*)?$");
private static final int FRONTEND_ID_GROUP = 1;
public static String from(FrontendId id, String name) {
return format("/%s/%s", id, name);
}
public static Optional<FrontendId> extractFrontendId(String path) {
Matcher matcher = SERVLET_PATH_WITH_FRONTEND_ID_PATTERN.matcher(path);
if (matcher.matches()) {
return Optional.of(FrontendId.from(matcher.group(FRONTEND_ID_GROUP)));
} else {
return Optional.empty();
}
}
}

View File

@@ -8,24 +8,51 @@ package org.cryptomator.frontend.webdav;
import static java.lang.Math.max;
import static java.lang.System.currentTimeMillis;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.http.HttpServletRequest;
import org.cryptomator.frontend.FrontendId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
class Tarpit {
class Tarpit implements Serializable {
private static final Logger LOG = LoggerFactory.getLogger(Tarpit.class);
private static final long DELAY_MS = 10000;
private final Set<FrontendId> validFrontendIds = new HashSet<>();
@Inject
public Tarpit() {}
public Tarpit() {
}
public void register(FrontendId frontendId) {
validFrontendIds.add(frontendId);
}
public void unregister(FrontendId frontendId) {
validFrontendIds.remove(frontendId);
}
public void handle(HttpServletRequest req) {
if (isRequestWithVaultId(req)) {
if (isRequestWithInvalidVaultId(req)) {
delayExecutionUninterruptibly();
LOG.debug("Delayed request to " + req.getRequestURI() + " by " + DELAY_MS + "ms");
}
}
private boolean isRequestWithInvalidVaultId(HttpServletRequest req) {
Optional<FrontendId> frontendId = ContextPaths.extractFrontendId(req.getServletPath());
return frontendId.isPresent() && !isValid(frontendId.get());
}
private void delayExecutionUninterruptibly() {
long expected = currentTimeMillis() + DELAY_MS;
long sleepTime = DELAY_MS;
@@ -38,9 +65,8 @@ class Tarpit {
}
}
private boolean isRequestWithVaultId(HttpServletRequest req) {
String path = req.getServletPath();
return path.matches("^/[a-zA-Z0-9_-]{12}/.*$");
private boolean isValid(FrontendId frontendId) {
return validFrontendIds.contains(frontendId);
}
}

View File

@@ -24,12 +24,15 @@ class WebDavFrontend implements Frontend {
private final WebDavMounterProvider webdavMounterProvider;
private final ServletContextHandler handler;
private final URI uri;
private final Runnable afterClose;
private WebDavMount mount;
public WebDavFrontend(WebDavMounterProvider webdavMounterProvider, ServletContextHandler handler, URI uri) throws FrontendCreationFailedException {
public WebDavFrontend(WebDavMounterProvider webdavMounterProvider, ServletContextHandler handler, URI uri, Runnable afterUnmount) throws FrontendCreationFailedException {
this.webdavMounterProvider = webdavMounterProvider;
this.handler = handler;
this.uri = uri;
this.afterClose = afterUnmount;
try {
handler.start();
} catch (Exception e) {
@@ -39,8 +42,12 @@ class WebDavFrontend implements Frontend {
@Override
public void close() throws Exception {
unmount();
handler.stop();
try {
unmount();
handler.stop();
} finally {
afterClose.run();
}
}
@Override
@@ -48,10 +55,10 @@ class WebDavFrontend implements Frontend {
mount = webdavMounterProvider.chooseMounter(mountParams).mount(uri, mountParams);
}
@Override
public void unmount() throws CommandFailedException {
private void unmount() throws CommandFailedException {
if (mount != null) {
mount.unmount();
mount = null;
}
}

View File

@@ -8,6 +8,8 @@
*******************************************************************************/
package org.cryptomator.frontend.webdav;
import static java.lang.String.format;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.BlockingQueue;
@@ -20,6 +22,7 @@ import org.cryptomator.filesystem.Folder;
import org.cryptomator.frontend.Frontend;
import org.cryptomator.frontend.FrontendCreationFailedException;
import org.cryptomator.frontend.FrontendFactory;
import org.cryptomator.frontend.FrontendId;
import org.cryptomator.frontend.webdav.mount.WebDavMounterProvider;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
@@ -45,9 +48,10 @@ public class WebDavServer implements FrontendFactory {
private final ContextHandlerCollection servletCollection;
private final WebDavServletContextFactory servletContextFactory;
private final WebDavMounterProvider webdavMounterProvider;
private final Tarpit tarpit;
@Inject
WebDavServer(WebDavServletContextFactory servletContextFactory, WebDavMounterProvider webdavMounterProvider, DefaultServlet defaultServlet) {
WebDavServer(WebDavServletContextFactory servletContextFactory, WebDavMounterProvider webdavMounterProvider, DefaultServlet defaultServlet, Tarpit tarpit) {
final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(MAX_PENDING_REQUESTS);
final ThreadPool tp = new QueuedThreadPool(MAX_THREADS, MIN_THREADS, THREAD_IDLE_SECONDS, queue);
this.server = new Server(tp);
@@ -55,7 +59,8 @@ public class WebDavServer implements FrontendFactory {
this.servletCollection = new ContextHandlerCollection();
this.servletContextFactory = servletContextFactory;
this.webdavMounterProvider = webdavMounterProvider;
this.tarpit = tarpit;
servletCollection.addHandler(defaultServlet.createServletContextHandler());
server.setConnectors(new Connector[] {localConnector});
server.setHandler(servletCollection);
@@ -103,10 +108,8 @@ public class WebDavServer implements FrontendFactory {
}
@Override
public Frontend create(Folder root, String contextPath) throws FrontendCreationFailedException {
if (!contextPath.startsWith("/")) {
throw new IllegalArgumentException("contextPath must begin with '/'");
}
public Frontend create(Folder root, FrontendId id, String name) throws FrontendCreationFailedException {
String contextPath = format("/%s/%s", id, name);
final URI uri;
try {
uri = new URI("http", null, "localhost", getPort(), contextPath, null, null);
@@ -114,8 +117,9 @@ public class WebDavServer implements FrontendFactory {
throw new IllegalStateException(e);
}
final ServletContextHandler handler = addServlet(root, uri);
tarpit.register(id);
LOG.info("Servlet available under " + uri);
return new WebDavFrontend(webdavMounterProvider, handler, uri);
return new WebDavFrontend(webdavMounterProvider, handler, uri, () -> tarpit.unregister(id));
}
}

View File

@@ -69,7 +69,8 @@ class ExclusiveSharedLockManager implements LockManager {
}
String token = DavConstants.OPAQUE_LOCK_TOKEN_PREFIX + UUID.randomUUID();
return lockedResources.computeIfAbsent(locator, loc -> new HashMap<>()).computeIfAbsent(token, t -> new ExclusiveSharedLock(t, lockInfo));
Map<String, ActiveLock> lockMap = Objects.requireNonNull(lockedResources.computeIfAbsent(locator, loc -> new HashMap<>()));
return lockMap.computeIfAbsent(token, t -> new ExclusiveSharedLock(t, lockInfo));
}
private void removedExpiredLocksInLocatorHierarchy(FileSystemResourceLocator locator) {

View File

@@ -86,7 +86,7 @@ final class LinuxGvfsDavMounter implements WebDavMounterStrategy {
@Override
public void reveal() throws CommandFailedException {
Script.fromLines("set -x", "xdg-open \"webdav:$DAV_SSP\"").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart()).execute();
Script.fromLines("set -x", "gvfs-open \"dav:$DAV_SSP\"").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart()).execute();
}
}

View File

@@ -86,7 +86,7 @@ final class LinuxGvfsWebDavMounter implements WebDavMounterStrategy {
@Override
public void reveal() throws CommandFailedException {
Script.fromLines("set -x", "xdg-open \"webdav:$DAV_SSP\"").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart()).execute();
Script.fromLines("set -x", "gvfs-open \"webdav:$DAV_SSP\"").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart()).execute();
}
}

View File

@@ -9,11 +9,11 @@
package org.cryptomator.frontend.webdav.mount;
import static java.util.stream.Collectors.toSet;
import static java.util.stream.IntStream.rangeClosed;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.Set;
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;
import javax.inject.Inject;
@@ -24,16 +24,21 @@ import org.apache.commons.lang3.SystemUtils;
import com.google.common.collect.Sets;
@Singleton
public final class WindowsDriveLetters {
private static final Set<Character> A_TO_Z = rangeClosed('A', 'Z').mapToObj(i -> (char) i).collect(toSet());
private static final Set<Character> A_TO_Z;
static {
try (IntStream stream = IntStream.rangeClosed('A', 'Z')) {
A_TO_Z = stream.mapToObj(i -> (char) i).collect(toSet());
}
}
@Inject
public WindowsDriveLetters() {
}
public Set<Character> getOccupiedDriveLetters() {
if (!SystemUtils.IS_OS_WINDOWS) {
throw new UnsupportedOperationException("This method is only defined for Windows file systems");
@@ -41,7 +46,7 @@ public final class WindowsDriveLetters {
Iterable<Path> rootDirs = FileSystems.getDefault().getRootDirectories();
return StreamSupport.stream(rootDirs.spliterator(), false).map(Path::toString).map(CharUtils::toChar).map(Character::toUpperCase).collect(toSet());
}
public Set<Character> getAvailableDriveLetters() {
return Sets.difference(A_TO_Z, getOccupiedDriveLetters());
}

View File

@@ -160,7 +160,7 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy {
private WindowsWebDavMount(String driveLetter) {
this.driveLetter = CharUtils.toCharacterObject(driveLetter);
this.openExplorerScript = fromLines("start explorer.exe " + driveLetter + ":");
this.unmountScript = fromLines("net use " + driveLetter + ": /delete");
this.unmountScript = fromLines("net use " + driveLetter + ": /delete /no");
}
@Override

View File

@@ -61,13 +61,18 @@ final class CommandRunner {
static CommandResult execute(Script script, long timeout, TimeUnit unit) throws CommandFailedException {
try {
final List<String> env = script.environment().entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.toList());
final String[] lines = script.getLines();
if (lines.length == 0) {
throw new IllegalArgumentException("Invalid script");
}
CommandResult result = null;
for (final String line : script.getLines()) {
for (final String line : lines) {
final String[] cmds = ArrayUtils.add(determineCli(), line);
final Process proc = Runtime.getRuntime().exec(cmds, env.toArray(new String[0]));
result = run(proc, timeout, unit);
result.assertOk();
}
assert result != null;
return result;
} catch (IOException e) {
throw new CommandFailedException(e);

View File

@@ -23,6 +23,8 @@ import org.cryptomator.ui.model.VaultObjectMapperProvider;
import org.cryptomator.ui.settings.Settings;
import org.cryptomator.ui.settings.SettingsProvider;
import org.cryptomator.ui.util.DeferredCloser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -34,6 +36,7 @@ import javafx.stage.Stage;
@Module(includes = {CryptoEngineModule.class, CommonsModule.class, WebDavModule.class})
class CryptomatorModule {
private static final Logger LOG = LoggerFactory.getLogger(CryptomatorModule.class);
private final Application application;
private final Stage mainWindow;
@@ -59,7 +62,13 @@ class CryptomatorModule {
@Singleton
DeferredCloser provideDeferredCloser() {
DeferredCloser closer = new DeferredCloser();
Cryptomator.addShutdownTask(closer::close);
Cryptomator.addShutdownTask(() -> {
try {
closer.close();
} catch (Exception e) {
LOG.error("Error during shutdown.", e);
}
});
return closer;
}

View File

@@ -12,6 +12,7 @@ import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.ExecutionException;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.ui.controllers.MainController;
@@ -126,7 +127,11 @@ public class MainApplication extends Application {
@Override
public void stop() {
closer.close();
try {
closer.close();
} catch (ExecutionException e) {
LOG.error("Error closing ressources", e);
}
}
}

View File

@@ -117,12 +117,11 @@ public class UnlockedController extends LocalizedFXMLViewController {
@FXML
private void didClickLockVault(ActionEvent event) {
asyncTaskService.asyncTaskOf(() -> {
vault.get().unmount();
vault.get().deactivateFrontend();
}).onError(CommandFailedException.class, () -> {
messageLabel.setText(localization.getString("unlocked.label.unmountFailed"));
}).andFinally(() -> {
}).onSuccess(() -> {
listener.ifPresent(listener -> listener.didLock(this));
}).onError(Exception.class, () -> {
messageLabel.setText(localization.getString("unlocked.label.unmountFailed"));
}).run();
}

View File

@@ -42,6 +42,7 @@ import org.cryptomator.frontend.Frontend;
import org.cryptomator.frontend.Frontend.MountParam;
import org.cryptomator.frontend.FrontendCreationFailedException;
import org.cryptomator.frontend.FrontendFactory;
import org.cryptomator.frontend.FrontendId;
import org.cryptomator.ui.settings.Settings;
import org.cryptomator.ui.util.DeferredClosable;
import org.cryptomator.ui.util.DeferredCloser;
@@ -73,7 +74,7 @@ public class Vault implements CryptoFileSystemDelegate {
private final Set<String> whitelistedResourcesWithInvalidMac = new HashSet<>();
private final AtomicReference<FileSystem> nioFileSystem = new AtomicReference<>();
private final String id;
private String mountName;
private Character winDriveLetter;
private Optional<StatsFileSystem> statsFileSystem = Optional.empty();
@@ -81,7 +82,8 @@ public class Vault implements CryptoFileSystemDelegate {
/**
* Package private constructor, use {@link VaultFactory}.
* @param string
*
* @param string
*/
Vault(String id, Path vaultDirectoryPath, ShorteningFileSystemFactory shorteningFileSystemFactory, CryptoFileSystemFactory cryptoFileSystemFactory, DeferredCloser closer) {
this.path = new SimpleObjectProperty<Path>(vaultDirectoryPath);
@@ -133,7 +135,7 @@ public class Vault implements CryptoFileSystemDelegate {
FileSystem normalizingFs = new NormalizedNameFileSystem(cryptoFs, SystemUtils.IS_OS_MAC_OSX ? Form.NFD : Form.NFC);
StatsFileSystem statsFs = new StatsFileSystem(normalizingFs);
statsFileSystem = Optional.of(statsFs);
Frontend frontend = frontendFactory.create(statsFs, contextPath());
Frontend frontend = frontendFactory.create(statsFs, FrontendId.from(id), stripStart(mountName, "/"));
filesystemFrontend = closer.closeLater(frontend);
frontend.mount(getMountParams(settings));
success = true;
@@ -146,11 +148,7 @@ public class Vault implements CryptoFileSystemDelegate {
}
}
private String contextPath() {
return String.format("/%s/%s", id, stripStart(mountName, "/"));
}
public synchronized void deactivateFrontend() {
public synchronized void deactivateFrontend() throws Exception {
filesystemFrontend.close();
statsFileSystem = Optional.empty();
Platform.runLater(() -> unlocked.set(false));
@@ -170,10 +168,6 @@ public class Vault implements CryptoFileSystemDelegate {
Optionals.ifPresent(filesystemFrontend.get(), Frontend::reveal);
}
public void unmount() throws CommandFailedException {
Optionals.ifPresent(filesystemFrontend.get(), Frontend::unmount);
}
// ******************************************************************************
// Delegate methods
// ********************************************************************************/
@@ -312,7 +306,7 @@ public class Vault implements CryptoFileSystemDelegate {
public void setWinDriveLetter(Character winDriveLetter) {
this.winDriveLetter = winDriveLetter;
}
public String getId() {
return id;
}

View File

@@ -8,18 +8,14 @@
*******************************************************************************/
package org.cryptomator.ui.model;
import static java.util.UUID.randomUUID;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.util.Base64;
import java.util.UUID;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.cryptomator.filesystem.crypto.CryptoFileSystemFactory;
import org.cryptomator.filesystem.shortening.ShorteningFileSystemFactory;
import org.cryptomator.frontend.FrontendId;
import org.cryptomator.ui.util.DeferredCloser;
@Singleton
@@ -41,34 +37,7 @@ public class VaultFactory {
}
public Vault createVault(Path path) {
return createVault(generateId(), path);
}
private String generateId() {
return asBase64String(nineBytesFrom(randomUUID()));
}
private String asBase64String(ByteBuffer bytes) {
ByteBuffer base64Buffer = Base64.getUrlEncoder().encode(bytes);
return new String(asByteArray(base64Buffer));
}
private ByteBuffer nineBytesFrom(UUID uuid) {
ByteBuffer uuidBuffer = ByteBuffer.allocate(9);
uuidBuffer.putLong(uuid.getMostSignificantBits());
uuidBuffer.put((byte)(uuid.getLeastSignificantBits() & 0xFF));
uuidBuffer.flip();
return uuidBuffer;
}
private byte[] asByteArray(ByteBuffer buffer) {
if (buffer.hasArray()) {
return buffer.array();
} else {
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
return bytes;
}
return createVault(FrontendId.generate().toString(), path);
}
}

View File

@@ -28,12 +28,6 @@ public interface DeferredClosable<T> extends AutoCloseable {
*/
public Optional<T> get();
/**
* Quietly closes the Object. If the object was closed before, nothing
* happens.
*/
public void close();
/**
* @return an empty object.
*/

View File

@@ -13,12 +13,10 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.cryptomator.common.ConsumerThrowingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.VisibleForTesting;
@@ -31,7 +29,7 @@ import com.google.common.annotations.VisibleForTesting;
*
* <p>
* If you have a {@link DeferredCloser} instance present, call
* {@link #closeLater(Object, Closer)} immediately after you have opened the
* {@link #closeLater(Object, ConsumerThrowingException)} immediately after you have opened the
* resource and return a resource handle. If {@link #close()} is called, the
* resource will be closed. Calling {@link DeferredClosable#close()} on the resource
* handle will also close the resource and prevent a second closing by
@@ -42,8 +40,6 @@ import com.google.common.annotations.VisibleForTesting;
*/
public class DeferredCloser implements AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(DeferredCloser.class);
@VisibleForTesting
final Map<Long, ManagedResource<?>> cleanups = new ConcurrentSkipListMap<>();
@@ -51,33 +47,32 @@ public class DeferredCloser implements AutoCloseable {
final AtomicLong counter = new AtomicLong();
private class ManagedResource<T> implements DeferredClosable<T> {
private final long number = counter.incrementAndGet();
private final AtomicReference<T> object = new AtomicReference<>();
private final T object;
private final ConsumerThrowingException<T, Exception> closer;
private boolean closed = false;
public ManagedResource(T object, ConsumerThrowingException<T, Exception> closer) {
super();
this.object.set(object);
this.closer = closer;
this.object = Objects.requireNonNull(object);
this.closer = Objects.requireNonNull(closer);
}
@Override
public void close() {
final T oldObject = object.getAndSet(null);
if (oldObject != null) {
cleanups.remove(number);
try {
closer.accept(oldObject);
} catch (Exception e) {
LOG.error("Closing resource failed.", e);
}
}
public synchronized void close() throws Exception {
closer.accept(object);
cleanups.remove(number);
closed = true;
}
@Override
public Optional<T> get() throws IllegalStateException {
return Optional.ofNullable(object.get());
if (closed) {
return Optional.empty();
} else {
return Optional.of(object);
}
}
}
@@ -85,11 +80,23 @@ public class DeferredCloser implements AutoCloseable {
* Closes all added objects which have not been closed before and releases references.
*/
@Override
public void close() {
public void close() throws ExecutionException {
ExecutionException exception = null;
for (Iterator<ManagedResource<?>> iterator = cleanups.values().iterator(); iterator.hasNext();) {
final ManagedResource<?> closableProvider = iterator.next();
closableProvider.close();
iterator.remove();
try {
closableProvider.close();
iterator.remove();
} catch (Exception e) {
if (exception == null) {
exception = new ExecutionException(e);
} else {
exception.addSuppressed(e);
}
}
}
if (exception != null) {
throw exception;
}
}