/*
* Copyright (C) 2013 lichtflut Forschungs- und Entwicklungsgesellschaft mbH
*
* 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.arastreju.sge.spi.impl;
import org.arastreju.sge.ConversationContext;
import org.arastreju.sge.context.Context;
import org.arastreju.sge.index.ArastrejuIndex;
import org.arastreju.sge.index.IndexSearcher;
import org.arastreju.sge.index.IndexUpdator;
import org.arastreju.sge.inferencing.implicit.InverseOfInferencer;
import org.arastreju.sge.inferencing.implicit.SubClassOfInferencer;
import org.arastreju.sge.inferencing.implicit.TypeInferencer;
import org.arastreju.sge.model.Statement;
import org.arastreju.sge.model.associations.AttachedAssociationKeeper;
import org.arastreju.sge.naming.QualifiedName;
import org.arastreju.sge.persistence.ResourceResolver;
import org.arastreju.sge.persistence.TxAction;
import org.arastreju.sge.persistence.TxResultAction;
import org.arastreju.sge.spi.AssociationResolver;
import org.arastreju.sge.spi.GraphDataConnection;
import org.arastreju.sge.spi.WorkingContext;
import org.arastreju.sge.spi.tx.BoundTransactionControl;
import org.arastreju.sge.spi.tx.TxProvider;
import org.arastreju.sge.spi.uow.AssociationManager;
import org.arastreju.sge.spi.uow.IndexUpdateUOW;
import org.arastreju.sge.spi.uow.InferencingInterceptor;
import org.arastreju.sge.spi.uow.OpenConversationNotifier;
import org.arastreju.sge.spi.uow.ResourceResolverImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* <p>
* Handler for resolving, adding and removing of a node's association.
* </p>
*
* <p>
* Created Dec 1, 2011
* </p>
*
* @author Oliver Tigges
*/
public class WorkingContextImpl implements WorkingContext {
public static final Context[] NO_CTX = new Context[0];
private static final Logger LOGGER = LoggerFactory.getLogger(WorkingContextImpl.class);
private static long ID_GEN = 0;
// ----------------------------------------------------
private final long ctxId = ++ID_GEN;
private final GraphDataConnection connection;
private final AssociationResolver associationResolver;
private final Map<QualifiedName, AttachedAssociationKeeper> register = new HashMap<QualifiedName, AttachedAssociationKeeper>();
private final Set<Context> readContexts = new HashSet<Context>();
private final TxProvider txProvider;
private AssociationManager associationManager;
private Context primaryContext;
private boolean active = true;
// ----------------------------------------------------
/**
* Creates a new Working Context.
*/
public WorkingContextImpl(GraphDataConnection connection) {
LOGGER.debug("New conversation context {} started.", ctxId);
this.connection = connection;
this.associationResolver = connection.createAssociationResolver(this);
this.txProvider = connection.createTxProvider(this);
connection.register(this);
}
// ----------------------------------------------------
@Override
public AttachedAssociationKeeper lookup(QualifiedName qn) {
assertActive();
AttachedAssociationKeeper registered = register.get(qn);
if (registered != null && !registered.isAttached()) {
LOGGER.warn("There is a detached AssociationKeeper in the conversation register: {}.", qn);
}
return registered;
}
@Override
public AttachedAssociationKeeper find(QualifiedName qn) {
assertActive();
AttachedAssociationKeeper registered = lookup(qn);
if (registered != null) {
return registered;
}
AttachedAssociationKeeper existing = connection.find(qn);
if (existing != null) {
attach(qn, existing);
return existing;
} else {
return null;
}
}
@Override
public AttachedAssociationKeeper create(final QualifiedName qn) {
assertActive();
AttachedAssociationKeeper keeper = getTxProvider().doTransacted(new TxResultAction<AttachedAssociationKeeper>() {
@Override
public AttachedAssociationKeeper execute() {
return connection.create(qn);
}
}, this);
attach(qn, keeper);
return keeper;
}
@Override
public void remove(final QualifiedName qn) {
assertActive();
detach(qn);
getTxProvider().doTransacted(new TxAction() {
@Override
public void execute() {
connection.remove(qn);
getIndexUpdator().remove(qn);
}
}, this);
}
// ----------------------------------------------------
@Override
public void attach(QualifiedName qn, AttachedAssociationKeeper keeper) {
assertActive();
register.put(qn, keeper);
keeper.setConversationContext(this);
}
@Override
public void detach(QualifiedName qn) {
assertActive();
final AttachedAssociationKeeper removed = register.remove(qn);
if (removed != null) {
removed.detach();
}
}
// ----------------------------------------------------
/**
* Resolve the associations of given association keeper.
* @param keeper The association keeper to be resolved.
*/
@Override
public void resolveAssociations(AttachedAssociationKeeper keeper) {
assertActive();
associationResolver.resolveAssociations(keeper);
}
@Override
public void addAssociation(final AttachedAssociationKeeper keeper, final Statement stmt) {
assertActive();
getTxProvider().doTransacted(new TxAction() {
@Override
public void execute() {
associationManager.addAssociation(keeper, stmt);
}
}, this);
}
@Override
public boolean removeAssociation(final AttachedAssociationKeeper keeper, final Statement stmt) {
assertActive();
getTxProvider().doTransacted(new TxAction() {
@Override
public void execute() {
associationManager.removeAssociation(keeper, stmt);
}
}, this);
return true;
}
// ----------------------------------------------------
@Override
public void onModification(QualifiedName qualifiedName, WorkingContext otherContext) {
AttachedAssociationKeeper existing = lookup(qualifiedName);
if (existing != null) {
LOGGER.info("Concurrent change on node {} in other context {}.", qualifiedName, otherContext);
existing.notifyChanged();
}
}
@Override
public void beginUnitOfWork(BoundTransactionControl tx) {
LOGGER.info("Starting new Unit of Work in conversation {}.", this);
this.associationManager = createAssociationManager(tx);
}
// ----------------------------------------------------
/**
* Clear the cache.
*/
@Override
public void clear() {
assertActive();
clearCaches();
}
/**
* Close and invalidate this context.
*/
@Override
public void close() {
if (active) {
clear();
onClose();
active = false;
LOGGER.info("Conversation '{}' will be closed.", ctxId);
}
}
public boolean isActive() {
return active;
}
// ----------------------------------------------------
@Override
public TxProvider getTxProvider() {
return txProvider;
}
@Override
public IndexSearcher getIndexSearcher() {
return new ArastrejuIndex(this, connection.getIndexProvider());
}
@Override
public IndexUpdator getIndexUpdator() {
ResourceResolverImpl resolver = new ResourceResolverImpl(this);
return new ArastrejuIndex(this, connection.getIndexProvider())
.add(new TypeInferencer(resolver))
.add(new SubClassOfInferencer(resolver));
}
// ----------------------------------------------------
public Context[] getReadContexts() {
assertActive();
if (readContexts != null) {
return readContexts.toArray(new Context[readContexts.size()]);
} else {
return NO_CTX;
}
}
public Context getPrimaryContext() {
return primaryContext;
}
@Override
public ConversationContext setPrimaryContext(Context ctx) {
this.primaryContext = ctx;
if (primaryContext != null) {
readContexts.add(primaryContext);
}
return this;
}
@Override
public ConversationContext setReadContexts(Context... ctxs) {
this.readContexts.clear();
if (ctxs != null) {
Collections.addAll(readContexts, ctxs);
}
if (primaryContext != null) {
readContexts.add(primaryContext);
}
return this;
}
// ----------------------------------------------------
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
WorkingContextImpl that = (WorkingContextImpl) o;
return ctxId == that.ctxId;
}
@Override
public int hashCode() {
return (int) (ctxId ^ (ctxId >>> 32));
}
@Override
public String toString() {
return "ConversationContext[" + ctxId + "]";
}
// ----------------------------------------------------
protected GraphDataConnection getConnection() {
return connection;
}
protected void onClose() {
connection.unregister(this);
}
protected void clearCaches() {
for (AttachedAssociationKeeper keeper : register.values()) {
keeper.detach();
}
register.clear();
}
protected void assertActive() {
if (!active) {
LOGGER.warn("Conversation context already closed: {}", ctxId);
throw new IllegalStateException("ConversationContext already closed.");
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
if (active) {
LOGGER.debug("Conversation context will be removed by GC, but has not been closed: {}", ctxId);
}
}
// ----------------------------------------------------
private AssociationManager createAssociationManager(BoundTransactionControl tx) {
ResourceResolver resolver = new ResourceResolverImpl(this);
IndexUpdateUOW indexUpdateUOW = new IndexUpdateUOW(getIndexUpdator());
tx.register(indexUpdateUOW);
AssociationManager am = new AssociationManager(resolver, tx);
am.register(connection.createAssociationWriter(this));
am.register(indexUpdateUOW);
am.register(new InferencingInterceptor(am).add(new InverseOfInferencer(resolver)));
am.register(new OpenConversationNotifier(getConnection(), this));
return am;
}
}