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

eu.fbk.knowledgestore.AbstractSession Maven / Gradle / Ivy

Go to download

The Core module (ks-core) contains core abstractions and basic functionalities shared by the KnowledgeStore Frontend Server and the Java Client. It also defines the Java version of the KnowledgeStore API.

The newest version!
package eu.fbk.knowledgestore;

import com.google.common.collect.Lists;
import eu.fbk.knowledgestore.Operation.*;
import eu.fbk.knowledgestore.Outcome.Status;
import eu.fbk.knowledgestore.data.*;
import eu.fbk.knowledgestore.vocabulary.NFO;
import eu.fbk.knowledgestore.vocabulary.NIE;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.query.BindingSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import javax.annotation.Nullable;
import java.io.Closeable;
import java.util.*;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

public abstract class AbstractSession implements Session {

    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractSession.class);

    private static final String MDC_ATTRIBUTE = "context";

    private static final long GRACE_PERIOD = 5000; // 5 sec in addition to the timeout set

    private static final long CLOSE_WAIT_TIME = 1000;

    private static long invocationCounter = 0;

    // Session-scoped variables

    private final Map namespaces;

    @Nullable
    private final String username;

    @Nullable
    private final String password;

    private final long creationTime;

    private final WeakHashMap pendingCloseables;

    private final AtomicBoolean closed;

    // Request-scoped variables

    private long timestamp;

    @Nullable
    private String operation;

    @Nullable
    private URI recordType;

    @Nullable
    private Class streamType;

    @Nullable
    private URI objectID;

    @Nullable
    private URI invocationID;

    @Nullable
    private String oldMDCAttribute;

    private long numCreated;

    private long numDeleted;

    private long numModified;

    private long numUnmodified;

    private final List failedOutcomes;

    @Nullable
    private ScheduledFuture interruptFuture;

    @Nullable
    private Thread interruptThread;

    protected AbstractSession(@Nullable final Map namespaces,
            @Nullable final String username, @Nullable final String password) {
        this.namespaces = namespaces != null ? namespaces : Data.newNamespaceMap();
        this.username = username;
        this.creationTime = System.currentTimeMillis();
        this.password = password;
        this.pendingCloseables = new WeakHashMap();
        this.closed = new AtomicBoolean(false);
        this.failedOutcomes = Lists.newArrayList();
    }

    @Nullable
    private void start(final String operation, @Nullable final URI recordType,
            @Nullable final Class streamType, @Nullable final URI objectID, @Nullable final Long timeout) {

        this.timestamp = System.currentTimeMillis();
        this.operation = operation;
        this.recordType = recordType;
        this.streamType = streamType;
        this.objectID = objectID;
        this.numCreated = 0L;
        this.numDeleted = 0L;
        this.numModified = 0L;
        this.numUnmodified = 0L;
        this.failedOutcomes.clear();
        this.oldMDCAttribute = MDC.get(MDC_ATTRIBUTE);

        URI invocationID = null;
        if (this.oldMDCAttribute != null) {
            try {
                invocationID = Data.getValueFactory().createURI(this.oldMDCAttribute);
            } catch (final Throwable ex) {
                // not valid
            }
        }
        if (invocationID == null) {
            final long ts = System.currentTimeMillis();
            final long counter;
            synchronized (AbstractSession.class) {
                ++AbstractSession.invocationCounter;
                if (AbstractSession.invocationCounter < ts) {
                    AbstractSession.invocationCounter = ts;
                }
                counter = AbstractSession.invocationCounter;
            }
            invocationID = Data.getValueFactory().createURI("req:" + Long.toString(counter, 32));
        }
        setInvocationID(invocationID);

        if (timeout != null) {
            final Thread thread = Thread.currentThread();
            this.interruptFuture = Data.getExecutor().schedule(new Runnable() {

                @Override
                public void run() {
                    thread.interrupt();
                }

            }, timeout + GRACE_PERIOD, TimeUnit.MILLISECONDS);
        }

        synchronized (this.closed) {
            this.interruptThread = Thread.currentThread();
        }
    }

    private void end() throws OperationException {
        try {
            if (this.interruptFuture != null && !this.interruptFuture.isDone()) {
                if (!this.interruptFuture.cancel(false)) {
                    try {
                        this.interruptFuture.get(); // being interrupted; await interruption
                    } catch (final Throwable ex) {
                        // ignore
                    }
                }
            }
            if (!this.failedOutcomes.isEmpty()) {
                throw fail(null); // thrown only if operation completed with some failed outcomes
            }
        } finally {
            MDC.put(MDC_ATTRIBUTE, this.oldMDCAttribute);
            this.interruptFuture = null;
            this.oldMDCAttribute = null;
            synchronized (this.closed) {
                this.interruptThread = null;
                Thread.interrupted(); // cancel interrupted status
                this.closed.notify(); // notify thread blocked on close(), if any
            }
        }
    }

    private  T filter(@Nullable final T closeable) {
        if (closeable != null) {
            this.pendingCloseables.put(closeable, null);
        }
        return closeable;
    }

    @Nullable
    private  Stream filter(@Nullable final Stream stream) {
        if (stream == null) {
            return null;
        }
        Stream result = stream;
        if (this.interruptFuture != null) {
            result = stream.setTimeout(System.currentTimeMillis()
                    + this.interruptFuture.getDelay(TimeUnit.MILLISECONDS));
        }
        this.pendingCloseables.put(result, null);

        return result;
    }

    private Handler filter(@Nullable final Handler handler) {
        return new Handler() {

            private boolean log = true;

            @Override
            public void handle(final Outcome outcome) throws Throwable {
                if (outcome != null) {
                    final Status status = outcome.getStatus();
                    if (status.isError()) {
                        AbstractSession.this.failedOutcomes.add(outcome);
                    } else if (status == Status.OK_CREATED) {
                        ++AbstractSession.this.numCreated;
                    } else if (status == Status.OK_DELETED) {
                        ++AbstractSession.this.numDeleted;
                    } else if (status == Status.OK_MODIFIED) {
                        ++AbstractSession.this.numModified;
                    } else {
                        ++AbstractSession.this.numUnmodified;
                    }
                }
                if (handler != null) {
                    try {
                        handler.handle(outcome);
                    } catch (final Throwable ex) {
                        if (this.log) {
                            this.log = false;
                            LOGGER.error("Handler failure: iteration being interrupted, "
                                    + "no additional exception from handler will be logged", ex);
                        }
                        Thread.currentThread().interrupt();
                    }
                }
            }

        };
    }

    private OperationException fail(@Nullable final Throwable ex) {
        if (!this.failedOutcomes.isEmpty()) {
            final int size = this.failedOutcomes.size();
            final List causes = Lists.newArrayListWithCapacity(size);
            for (final Outcome failedOutcome : this.failedOutcomes) {
                causes.add(new OperationException(failedOutcome));
            }
            this.failedOutcomes.clear();
            if (ex != null) {
                causes.add(ex);
            }
            return new OperationException(outcome(Status.ERROR_BULK, null), causes);
        } else if (ex instanceof OperationException) {
            return (OperationException) ex;
        } else if (ex != null) {
            final AtomicReference message = new AtomicReference(ex.getMessage());
            Status status = Status.ERROR_UNEXPECTED;
            try {
                status = doFail(ex, message);
            } catch (final Throwable ex2) {
                LOGGER.warn("Could not map " + ex.getClass().getSimpleName()
                        + " to status / message", ex);
            }
            return new OperationException(outcome(status, message.get()), ex);
        } else {
            return new OperationException(outcome(Status.ERROR_UNEXPECTED, null));
        }
    }

    private Outcome outcome(final Status status, @Nullable final String message) {
        final long elapsed = System.currentTimeMillis() - this.timestamp;
        final StringBuilder builder = new StringBuilder();
        final long numObjects = this.numCreated + this.numDeleted + this.numModified
                + this.numUnmodified;
        if (numObjects > 0 || status == Status.OK_BULK || status == Status.ERROR_BULK) {
            builder.append(numObjects).append(' ')
                    .append(this.recordType.getLocalName().toLowerCase()).append("(s) involved");
            if (this.numCreated > 0) {
                builder.append(", ").append(this.numCreated).append(" created");
            }
            if (this.numModified > 0) {
                builder.append(", ").append(this.numModified).append(" modified");
            }
            if (this.numUnmodified > 0) {
                builder.append(", ").append(this.numUnmodified).append(" unmodified");
            }
            if (this.numDeleted > 0) {
                builder.append(", ").append(this.numDeleted).append(" deleted");
            }
            if (!this.failedOutcomes.isEmpty()) {
                builder.append(", ").append(this.failedOutcomes.size()).append(" failed");
            }
            if (numObjects == 0) {
                if (message != null) {
                    builder.append(", ");
                }
            }
        }
        if (message != null) {
            builder.append(message);
        }
        if (builder.length() > 0) {
            builder.append(", ");
        }
        builder.append(elapsed).append(" ms");
        return Outcome.create(status, this.invocationID, this.objectID, builder.toString());
    }

    private void logRequest(final Object... args) {
        if (LOGGER.isInfoEnabled()) {
            final StringBuilder builder = new StringBuilder();
            builder.append(this.operation.toUpperCase());
            if (this.recordType != null) {
                builder.append(' ').append(this.recordType.getLocalName().toUpperCase());
            }
            String separator = " ";
            for (int i = 0; i < args.length; i += 2) {
                final Object name = args[i];
                final Object value = args[i + 1];
                if (value != null) {
                    builder.append(separator);
                    if (name != null) {
                        builder.append(name).append('=');
                    }
                    if (value instanceof Collection && ((Collection) value).size() > 10) {
                        builder.append("[...").append(((Collection) value).size())
                                .append(" elements...]");
                    } else {
                        builder.append(value);
                    }
                    separator = ", ";
                }
            }
            LOGGER.info(builder.toString());
        }
    }

    private  T logResponse(final T result) {
        if (LOGGER.isInfoEnabled()) {
            final long elapsed = System.currentTimeMillis() - this.timestamp;
            if (result instanceof Outcome) {
                final Outcome outcome = (Outcome) result;
                final String message = outcome.getMessage();
                LOGGER.info("Result: {}{}, {} ms", outcome.getStatus(), message == null ? ""
                        : ", " + message, elapsed);
            } else if (result instanceof Long) {
                LOGGER.info("Result: {}, {} ms", result, elapsed);
            } else if (result instanceof Representation) {
                final Record meta = ((Representation) result).getMetadata();
                final String file = meta.getUnique(NFO.FILE_NAME, String.class, "unnamed file");
                final String type = meta.getUnique(NIE.MIME_TYPE, String.class, "unknown type");
                final long size = meta.getUnique(NFO.FILE_SIZE, Long.class, -1L);
                LOGGER.info("Result: {}, {}, {}, {} ms", file, type, size >= 0 ? size + " bytes"
                        : "unknown size", elapsed);
            } else if (result instanceof Stream) {
                LOGGER.info("Result: {} stream, {} ms",
                        this.streamType == BindingSet.class ? "tuple" : this.streamType
                                .getSimpleName().toLowerCase(), elapsed);
            } else if (result == null) {
                LOGGER.info("Result: null, {} ms", elapsed);
            }
        }
        return result;
    }

    @Override
    @Nullable
    public final String getUsername() throws IllegalStateException {
        checkNotClosed();
        return this.username;
    }

    @Override
    public final String getPassword() throws IllegalStateException {
        checkNotClosed();
        return this.password;
    }

    @Override
    public final Map getNamespaces() throws IllegalStateException {
        checkNotClosed();
        return this.namespaces;
    }

    @Override
    public final Download download(final URI resourceID) throws IllegalStateException {
        checkNotClosed();
        return new Download(this.namespaces, resourceID) {

            @Override
            @Nullable
            protected Representation doExec(@Nullable final Long timeout, final URI id,
                    @Nullable final Set mimeTypes, final boolean caching)
                    throws OperationException {

                synchronized (AbstractSession.this) {
                    checkNotClosed();
                    start("DOWNLOAD", null, null, id, timeout);
                    try {
                        logRequest(null, id, "accept", mimeTypes, //
                                null, caching ? null : "no caching", "timeout", timeout);
                        return logResponse(filter(doDownload(timeout, id, mimeTypes, caching)));
                    } catch (final Throwable ex) {
                        throw fail(ex);
                    } finally {
                        end();
                    }
                }

            }

        };
    }

    @Override
    public final Upload upload(final URI resourceID) throws IllegalStateException {
        checkNotClosed();
        return new Upload(this.namespaces, resourceID) {

            @Override
            protected Outcome doExec(@Nullable final Long timeout, final URI id,
                    @Nullable final Representation representation) throws OperationException {
                synchronized (AbstractSession.this) {
                    checkNotClosed();
                    start("UPLOAD", null, null, id, timeout);
                    try {
                        logRequest(null, id, "content", representation != null, "timeout", timeout);
                        return logResponse(doUpload(timeout, resourceID, representation));
                    } catch (final Throwable ex) {
                        throw fail(ex);
                    } finally {
                        end();
                    }
                }
            }

        };
    }

    @Override
    public final Count count(final URI type) throws IllegalStateException {
        checkNotClosed();
        return new Count(this.namespaces, type) {

            @Override
            protected long doExec(@Nullable final Long timeout, final URI type,
                    @Nullable final XPath condition, @Nullable final Set ids)
                    throws OperationException {
                synchronized (AbstractSession.this) {
                    checkNotClosed();
                    start("COUNT", type, null, null, timeout);
                    try {
                        logRequest(null, condition, "ids", ids, "timeout", timeout);
                        return logResponse(doCount(timeout, type, condition, ids));
                    } catch (final Throwable ex) {
                        throw fail(ex);
                    } finally {
                        end();
                    }
                }
            }

        };
    }

    @Override
    public final Retrieve retrieve(final URI type) throws IllegalStateException {
        checkNotClosed();
        return new Retrieve(this.namespaces, type) {

            @Override
            protected Stream doExec(@Nullable final Long timeout, final URI type,
                    @Nullable final XPath condition, @Nullable final Set ids,
                    @Nullable final Set properties, @Nullable final Long offset,
                    @Nullable final Long limit) throws OperationException {
                synchronized (AbstractSession.this) {
                    checkNotClosed();
                    start("RETRIEVE", type, Record.class, null, timeout);
                    try {
                        logRequest(null, condition, "ids", ids, "props", properties, //
                                "offset", offset, "limit", limit, "timeout", timeout);
                        return logResponse(doRetrieve(timeout, type, condition, ids, properties,
                                offset, limit));
                    } catch (final Throwable ex) {
                        throw fail(ex);
                    } finally {
                        end();
                    }
                }
            }

        };
    }

    @Override
    public final Create create(final URI type) throws IllegalStateException {
        checkNotClosed();
        return new Create(this.namespaces, type) {

            @Override
            protected Outcome doExec(@Nullable final Long timeout, final URI type,
                    @Nullable final Stream records,
                    final Handler handler) throws OperationException {
                synchronized (AbstractSession.this) {
                    checkNotClosed();
                    start("CREATE", type, null, null, timeout);
                    try {
                        logRequest("timeout", timeout);
                        doCreate(timeout, type, records, filter(handler));
                        return logResponse(outcome(Status.OK_BULK, null));
                    } catch (final Throwable ex) {
                        throw fail(ex);
                    } finally {
                        end();
                    }
                }
            }

        };
    }

    @Override
    public final Merge merge(final URI type) throws IllegalStateException {
        checkNotClosed();
        return new Merge(this.namespaces, type) {

            @Override
            protected Outcome doExec(@Nullable final Long timeout, final URI type,
                    @Nullable final Stream records,
                    @Nullable final Criteria criteria, final Handler handler)
                    throws OperationException {
                synchronized (AbstractSession.this) {
                    checkNotClosed();
                    start("MERGE", type, null, null, timeout);
                    try {
                        logRequest(null, criteria, "timeout", timeout);
                        doMerge(timeout, type, records, criteria, filter(handler));
                        return logResponse(outcome(Status.OK_BULK, null));
                    } catch (final Throwable ex) {
                        throw fail(ex);
                    } finally {
                        end();
                    }
                }
            }

        };
    }

    @Override
    public final Update update(final URI type) throws IllegalStateException {
        checkNotClosed();
        return new Update(this.namespaces, type) {

            @Override
            protected Outcome doExec(@Nullable final Long timeout, final URI type,
                    @Nullable final XPath condition, @Nullable final Set ids,
                    @Nullable final Record record, @Nullable final Criteria criteria,
                    final Handler handler) throws OperationException {
                synchronized (AbstractSession.this) {
                    checkNotClosed();
                    start("UPDATE", type, null, null, timeout);
                    try {
                        logRequest(null, criteria, null, condition, "ids", ids, "timeout", timeout);
                        doUpdate(timeout, type, condition, ids, record, criteria, filter(handler));
                        return logResponse(outcome(Status.OK_BULK, null));
                    } catch (final Throwable ex) {
                        throw fail(ex);
                    } finally {
                        end();
                    }
                }
            }

        };
    }

    @Override
    public final Delete delete(final URI type) throws IllegalStateException {
        checkNotClosed();
        return new Delete(this.namespaces, type) {

            @Override
            protected Outcome doExec(@Nullable final Long timeout, final URI type,
                    @Nullable final XPath condition, @Nullable final Set ids,
                    final Handler handler) throws OperationException {
                synchronized (AbstractSession.this) {
                    checkNotClosed();
                    start("DELETE", type, null, null, timeout);
                    try {
                        logRequest(null, condition, "ids", ids, "timeout", timeout);
                        doDelete(timeout, type, condition, ids, filter(handler));
                        return logResponse(outcome(Status.OK_BULK, null));
                    } catch (final Throwable ex) {
                        throw fail(ex);
                    } finally {
                        end();
                    }
                }
            }

        };
    }

    @Override
    public final Match match() throws IllegalStateException {
        checkNotClosed();
        return new Match(this.namespaces) {

            @Override
            protected Stream doExec(@Nullable final Long timeout,
                    final Map conditions, final Map> ids,
                    final Map> properties) throws OperationException {
                synchronized (AbstractSession.this) {
                    checkNotClosed();
                    start("MATCH", null, Record.class, null, timeout);
                    try {
                        logRequest(null, conditions, "ids", ids, "props", //
                                properties, "timeout", timeout);
                        return logResponse(filter(doMatch(timeout, conditions, ids, properties)));
                    } catch (final Throwable ex) {
                        throw fail(ex);
                    } finally {
                        end();
                    }
                }
            }

        };
    }

    @Override
    public final Sparql sparql(final String expression, final Object... arguments)
            throws IllegalStateException {
        checkNotClosed();
        return new Sparql(this.namespaces, expression, arguments) {

            @Override
            protected  Stream doExec(@Nullable final Long timeout, final Class type,
                    final String expression, @Nullable final Set defaultGraphs,
                    @Nullable final Set namedGraphs) throws OperationException {
                synchronized (AbstractSession.this) {
                    checkNotClosed();
                    start("SPARQL", null, type, null, timeout);
                    try {
                        logRequest("from", defaultGraphs, "from-named", namedGraphs, //
                                "timeout", timeout, null, expression);
                        return logResponse(filter(doSparql(timeout, type, expression,
                                defaultGraphs, namedGraphs)));
                    } catch (final Throwable ex) {
                        throw fail(ex);
                    } finally {
                        end();
                    }
                }
            }

        };
    }

	@Override
	public final SparqlUpdate sparqlupdate()
			throws IllegalStateException {
		checkNotClosed();
		return new SparqlUpdate(this.namespaces) {
			@Override
			protected Outcome doExec(@Nullable Long timeout, @Nullable Stream statements) throws OperationException {
				synchronized (AbstractSession.this) {
					checkNotClosed();
					start("SPARQLUPDATE", null, null, null, timeout);
					try {
						logRequest("timeout", timeout);
						return logResponse(doSparqlUpdate(timeout, statements));
					} catch (final Throwable ex) {
						throw fail(ex);
					} finally {
						end();
					}
				}
			}
		};
	}

    @Override
    public final SparqlDelete sparqldelete()
            throws IllegalStateException {
        checkNotClosed();
        return new SparqlDelete(this.namespaces) {
            @Override
            protected Outcome doExec(@Nullable Long timeout, @Nullable Stream statements) throws OperationException {
                synchronized (AbstractSession.this) {
                    checkNotClosed();
                    start("SPARQLDELETE", null, null, null, timeout);
                    try {
                        logRequest("timeout", timeout);
                        return logResponse(doSparqlDelete(timeout, statements));
                    } catch (final Throwable ex) {
                        throw fail(ex);
                    } finally {
                        end();
                    }
                }
            }
        };
    }

    @Override
    public final boolean isClosed() {
        return this.closed.get();
    }

    @Override
    public final void close() {
        if (this.closed.compareAndSet(false, true)) {
            synchronized (this.closed) {
                if (this.interruptThread != null) {
                    this.interruptThread.interrupt(); // attempt interrupting current operation
                    try {
                        this.closed.wait(CLOSE_WAIT_TIME);

                    } catch (final InterruptedException ex) {
                        // ignore and proceed with closing resources
                    }
                }
            }
            for (final Closeable closeable : this.pendingCloseables.keySet()) {
                try {
                    closeable.close();
                } catch (final Throwable ex) {
                    LOGGER.error("Error closing " + closeable.getClass().getSimpleName()
                            + " after session has been closed", ex);
                }
            }
            doClose(); // custom close actions
        }
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + "[" + (isClosed() ? "closed" : "open") + ", "
                + this.username + ", " + new Date(this.creationTime).toString() + ")";
    }

    protected final void checkNotClosed() {
        if (this.closed.get()) {
            throw new IllegalStateException("Session has been closed");
        }
    }

    protected final URI getInvocationID() {
        return this.invocationID;
    }

    protected final void setInvocationID(final URI invocationID) {
        this.invocationID = invocationID;
        MDC.put(MDC_ATTRIBUTE, invocationID == null ? null : invocationID.stringValue());
    }

    protected Status doFail(final Throwable ex, final AtomicReference message)
            throws Throwable {
        return Status.ERROR_UNEXPECTED;
    }

    protected abstract Representation doDownload(@Nullable Long timeout, final URI id,
            @Nullable final Set mimeTypes, final boolean useCaches) throws Throwable;

    protected abstract Outcome doUpload(@Nullable Long timeout, final URI resourceID,
            @Nullable final Representation representation) throws Throwable;

    protected abstract long doCount(@Nullable Long timeout, final URI type,
            @Nullable final XPath condition, @Nullable final Set ids) throws Throwable;

    protected abstract Stream doRetrieve(@Nullable Long timeout, final URI type,
            @Nullable final XPath condition, @Nullable final Set ids,
            @Nullable final Set properties, @Nullable Long offset, @Nullable Long limit)
            throws Throwable;

    protected abstract void doCreate(@Nullable Long timeout, final URI type,
            @Nullable final Stream records,
            final Handler handler) throws Throwable;

    protected abstract void doMerge(@Nullable Long timeout, final URI type,
            @Nullable final Stream records, @Nullable final Criteria criteria,
            final Handler handler) throws Throwable;

    protected abstract void doUpdate(@Nullable Long timeout, final URI type,
            @Nullable final XPath condition, @Nullable final Set ids,
            @Nullable final Record record, @Nullable final Criteria criteria,
            final Handler handler) throws Throwable;

    protected abstract void doDelete(@Nullable Long timeout, final URI type,
            @Nullable final XPath condition, @Nullable final Set ids,
            final Handler handler) throws Throwable;

    protected abstract Stream doMatch(@Nullable Long timeout,
            final Map conditions, final Map> ids,
            final Map> properties) throws Throwable;

    protected abstract  Stream doSparql(@Nullable Long timeout, final Class type,
            final String expression, @Nullable final Set defaultGraphs,
            @Nullable final Set namedGraphs) throws Throwable;

    protected abstract Outcome doSparqlUpdate(@Nullable Long timeout,
													@Nullable final Stream statements) throws Throwable;

    protected abstract Outcome doSparqlDelete(@Nullable Long timeout,
                                              @Nullable final Stream statements) throws Throwable;

    protected void doClose() {
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy