All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.java.truevfs.kernel.impl.FalsePositiveArchiveController Maven / Gradle / Ivy

/*
 * Copyright © 2005 - 2021 Schlichtherle IT Services.
 * All rights reserved. Use is subject to license terms.
 */
package net.java.truevfs.kernel.impl;

import bali.Cache;
import net.java.truecommons.cio.*;
import net.java.truecommons.shed.BitField;
import net.java.truecommons.shed.ControlFlowException;
import net.java.truevfs.kernel.spec.*;

import javax.annotation.CheckForNull;
import javax.annotation.concurrent.ThreadSafe;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.SeekableByteChannel;
import java.util.Map;

import static bali.CachingStrategy.NOT_THREAD_SAFE;
import static net.java.truevfs.kernel.spec.FsNodeName.ROOT;

/**
 * Implements a chain of responsibility for resolving
 * {@link FalsePositiveArchiveException}s which may get thrown by its decorated file system controller.
 * 

* This controller is a barrier for {@code FalsePositiveArchiveException}s: * Whenever the decorated controller chain throws a {@code FalsePositiveArchiveException}, the file system operation is * routed to the controller of the parent file system in order to continue the operation. * If this fails with an {@link IOException}, then the {@code IOException} which is associated as the original cause of * the initial {@code FalsePositiveArchiveException} gets rethrown. *

* This algorithm effectively achieves the following objectives: *

    *
  1. False positive archive files get resolved correctly by accessing them as entities of the parent file system. *
  2. If the file system driver for the parent file system throws another exception, then it gets discarded and the * exception initially thrown by the file system driver for the false positive archive file takes its place in order * to provide the caller with a good indication of what went wrong in the first place. *
  3. Non-{@code IOException}s are excluded from this masquerade in order to support resolving them by a more competent * caller. * This is required to make {@link net.java.truecommons.shed.ControlFlowException}s work as designed. *
*

* As an example consider accessing a RAES encrypted ZIP file: * With the default driver configuration of the module TrueVFS ZIP.RAES, whenever a ZIP.RAES file gets mounted, the user * is prompted for a password. * If the user cancels the password prompting dialog, then an appropriate exception gets thrown. * The target archive controller would then catch this exception and flag the archive file as a false positive by * wrapping this exception in a {@code FalsePositiveArchiveException}. * This class would then catch this false positive exception and try to resolve the issue by using the parent file * system controller. * Failing that, the initial exception would get rethrown in order to signal to the caller that the user had cancelled * password prompting. * * @author Christian Schlichtherle * @see FalsePositiveArchiveException */ @ThreadSafe abstract class FalsePositiveArchiveController implements FsDelegatingController { private final State tryChild = new State() { @Override public T apply(FsNodeName name, Op op) throws IOException { return op.call(getController(), name); } }; private volatile State state = tryChild; @Cache(NOT_THREAD_SAFE) FsNodePath getPath() { return getModel().getMountPoint().getPath(); } private FsNodeName parent(FsNodeName name) { return getPath().resolve(name).getNodeName(); } @CheckForNull @Override public FsNode node(BitField options, FsNodeName name) throws IOException { return apply(name, (c, n) -> c.node(options, n)); } @Override public void checkAccess( BitField options, FsNodeName name, BitField types ) throws IOException { apply(name, (c, n) -> { c.checkAccess(options, n, types); return null; }); } @Override public void setReadOnly(BitField options, FsNodeName name) throws IOException { apply(name, (c, n) -> { c.setReadOnly(options, n); return null; }); } @Override public boolean setTime( BitField options, FsNodeName name, Map times ) throws IOException { return apply(name, (c, n) -> c.setTime(options, n, times)); } @Override public boolean setTime( BitField options, FsNodeName name, BitField types, long value ) throws IOException { return apply(name, (c, n) -> c.setTime(options, n, types, value)); } @Override public InputSocket input(BitField options, FsNodeName name) { return new AbstractInputSocket() { FsController last; InputSocket socket; InputSocket socket(FsController c, FsNodeName n) { if (last != c) { last = c; socket = c.input(options, n); } return socket; } @Override public Entry target() throws IOException { return apply(name, (c, n) -> socket(c, n).target()); } @Override public InputStream stream(OutputSocket peer) throws IOException { return apply(name, (c, n) -> socket(c, n).stream(peer)); } @Override public SeekableByteChannel channel(OutputSocket peer) throws IOException { return apply(name, (c, n) -> socket(c, n).channel(peer)); } }; } @Override public OutputSocket output( BitField options, FsNodeName name, @CheckForNull Entry template ) { return new AbstractOutputSocket() { FsController last; OutputSocket socket; OutputSocket socket(FsController c, FsNodeName n) { if (last != c) { last = c; socket = c.output(options, n, template); } return socket; } @Override public Entry target() throws IOException { return apply(name, (c, n) -> socket(c, n).target()); } @Override public OutputStream stream(InputSocket peer) throws IOException { return apply(name, (c, n) -> socket(c, n).stream(peer)); } @Override public SeekableByteChannel channel(InputSocket peer) throws IOException { return apply(name, (c, n) -> socket(c, n).channel(peer)); } }; } @Override public void make( BitField options, FsNodeName name, Entry.Type type, @CheckForNull Entry template ) throws IOException { apply(name, (c, n) -> { c.make(options, n, type, template); return null; }); } @Override public void unlink(final BitField options, final FsNodeName name) throws IOException { final Op op = (c, n) -> { c.unlink(options, n); if (n.isRoot()) { assert c == getController(); // Unlink target archive file from parent file system. // This operation isn't lock protected, so it's not atomic! getParent().unlink(options, parent(n)); } return null; }; if (name.isRoot()) { // HC SVNT DRACONES! try { tryChild.apply(ROOT, op); } catch (FalsePositiveArchiveException e) { new UseParent(e).apply(ROOT, op); } state = tryChild; } else { apply(name, op); } } @Override public void sync(final BitField options) throws FsSyncException { // HC SVNT DRACONES! try { getController().sync(options); } catch (final FsSyncException | ControlFlowException e) { assert state == tryChild; throw e; } state = tryChild; } private T apply(final FsNodeName name, final Op op) throws IOException { State state = this.state; try { return state.apply(name, op); } catch (final FalsePositiveArchiveException e) { assert state == tryChild; state = new UseParent(e); if (e instanceof PersistentFalsePositiveArchiveException) { this.state = state; } return state.apply(name, op); } } @FunctionalInterface private interface Op { T call(FsController c, FsNodeName n) throws IOException; } @FunctionalInterface private interface State { T apply(FsNodeName name, Op op) throws IOException; } private final class UseParent implements State { private final FalsePositiveArchiveException original; private final IOException originalCause; private UseParent(final FalsePositiveArchiveException original) { this.original = original; this.originalCause = original.getCause(); } @Override public T apply(final FsNodeName name, final Op op) throws IOException { try { return op.call(getParent(), parent(name)); } catch (FalsePositiveArchiveException e) { throw new AssertionError(e); } catch (final IOException e) { if (originalCause != e) { originalCause.addSuppressed(e); } throw originalCause; } catch (final Throwable e) { assert !(e instanceof ControlFlowException) || e instanceof NeedsLockRetryException; e.addSuppressed(original); // provide full context throw e; } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy