package xdi2.messaging.container.impl; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import xdi2.core.Graph; import xdi2.core.constants.XDIConstants; import xdi2.core.features.nodetypes.XdiPeerRoot; import xdi2.core.syntax.XDIAddress; import xdi2.core.syntax.XDIArc; import xdi2.core.syntax.XDIStatement; import xdi2.core.util.iterators.IterableIterator; import xdi2.core.util.iterators.IteratorListMaker; import xdi2.messaging.Message; import xdi2.messaging.MessageEnvelope; import xdi2.messaging.container.AddressHandler; import xdi2.messaging.container.Extension; import xdi2.messaging.container.MessagingContainer; import xdi2.messaging.container.StatementHandler; import xdi2.messaging.container.contributor.Contributor; import xdi2.messaging.container.contributor.ContributorMap; import xdi2.messaging.container.contributor.ContributorResult; import xdi2.messaging.container.contributor.impl.digest.GenerateDigestSecretTokenContributor; import xdi2.messaging.container.contributor.impl.keygen.GenerateKeyContributor; import xdi2.messaging.container.exceptions.Xdi2MessagingException; import xdi2.messaging.container.execution.ExecutionContext; import xdi2.messaging.container.execution.ExecutionResult; import xdi2.messaging.container.interceptor.Interceptor; import xdi2.messaging.container.interceptor.InterceptorList; import xdi2.messaging.container.interceptor.InterceptorResult; import xdi2.messaging.container.interceptor.impl.HasInterceptor; import xdi2.messaging.container.interceptor.impl.RefInterceptor; import xdi2.messaging.container.interceptor.impl.ToInterceptor; import xdi2.messaging.container.interceptor.impl.connect.ConnectInterceptor; import xdi2.messaging.container.interceptor.impl.defer.DeferResultInterceptor; import xdi2.messaging.container.interceptor.impl.linkcontract.LinkContractInterceptor; import xdi2.messaging.container.interceptor.impl.push.PushInInterceptor; import xdi2.messaging.container.interceptor.impl.push.PushOutInterceptor; import xdi2.messaging.container.interceptor.impl.security.digest.DigestInterceptor; import xdi2.messaging.container.interceptor.impl.security.secrettoken.SecretTokenInterceptor; import xdi2.messaging.container.interceptor.impl.security.signature.SignatureInterceptor; import xdi2.messaging.container.interceptor.impl.send.SendInterceptor; import xdi2.messaging.container.interceptor.impl.signing.SigningInterceptor; import xdi2.messaging.operations.Operation; /** * The AbstractMessagingContainer provides the following functionality: * - Implementation of execute() with a message envelope (all messages * in the envelope and all operations in the messages will be executed). * - Implementation of execute() with a message (all operations * in the messages will be executed). * - Support for interceptors and contributors. * - Maintaining an "execution context" object where state can be kept between * individual phases. * * Subclasses must do the following: * - Implement execute() with an operation. * * @author markus */ public abstract class AbstractMessagingContainer implements MessagingContainer { private static final Logger log = LoggerFactory.getLogger(AbstractMessagingContainer.class); private XDIArc ownerPeerRootXDIArc; private InterceptorList<MessagingContainer> interceptors; private ContributorMap contributors; public AbstractMessagingContainer(XDIArc ownerPeerRootXDIArc) { this.ownerPeerRootXDIArc = ownerPeerRootXDIArc; this.interceptors = new InterceptorList<MessagingContainer> (); this.contributors = new ContributorMap(); } public AbstractMessagingContainer() { this((XDIArc) null); } @Override public void init() throws Exception { if (log.isInfoEnabled()) log.info("Initializing " + this.getClass().getSimpleName() + " [" + this.getInterceptors().size() + " interceptors: " + this.getInterceptors().stringList() + "] [" + this.getContributors().size() + " contributors: " + this.getContributors().stringList() + "]."); // init interceptors and contributors List<Extension<MessagingContainer>> extensions = new ArrayList<Extension<MessagingContainer>> (); extensions.addAll(new IteratorListMaker<Interceptor<MessagingContainer>> (this.getInterceptors().iterator()).list()); extensions.addAll(new IteratorListMaker<Contributor> (this.getContributors().iterator()).list()); Collections.sort(extensions, new Extension.InitPriorityComparator()); for (Extension<MessagingContainer> extension : extensions) { if (log.isDebugEnabled()) log.debug("Initializing extension " + extension.getClass().getSimpleName() + "."); extension.init(this); } } @Override public void shutdown() throws Exception { if (log.isInfoEnabled()) log.info("Shutting down " + this.getClass().getSimpleName() + "."); // shutdown interceptors and contributors List<Extension<MessagingContainer>> extensions = new ArrayList<Extension<MessagingContainer>> (); extensions.addAll(new IteratorListMaker<Interceptor<MessagingContainer>> (this.getInterceptors().iterator()).list()); extensions.addAll(new IteratorListMaker<Contributor> (this.getContributors().iterator()).list()); Collections.sort(extensions, new Extension.ShutdownPriorityComparator()); for (Extension<MessagingContainer> extension : extensions) { if (log.isDebugEnabled()) log.debug("Shutting down extension " + extension.getClass().getSimpleName() + "."); extension.shutdown(this); } } /** * Executes a messaging request against this messaging container. * @param messageEnvelope The XDI message envelope to be executed. * @param executionContext An "execution context" object that carries state between * messaging containers, interceptors and contributors. * @param executionResult The execution result produced by executing the messaging request. */ @Override public void execute(MessageEnvelope messageEnvelope, ExecutionContext executionContext, ExecutionResult executionResult) throws Xdi2MessagingException { if (messageEnvelope == null) throw new NullPointerException(); if (executionContext == null) throw new NullPointerException(); if (executionResult == null) throw new NullPointerException(); if (log.isDebugEnabled()) log.debug(this.getClass().getSimpleName() + ": Executing message envelope (" + messageEnvelope.getMessageCount() + " messages)."); boolean skipMessagingContainer = false; try { // push executionContext.pushMessagingContainer(this, null); // push executionContext.pushMessageEnvelope(messageEnvelope, null); // reset execution context executionContext.resetMessageEnvelopeAttributes(); // before message envelope skipMessagingContainer |= this.before(messageEnvelope, executionContext, executionResult); // execute message envelope interceptors (before) InterceptorResult interceptorResultBefore = InterceptorExecutor.executeMessageEnvelopeInterceptorsBefore(this.getInterceptors(), messageEnvelope, executionContext, executionResult); if (interceptorResultBefore.isSkipMessagingContainer()) { if (log.isDebugEnabled()) log.debug("Skipping messaging container according to message envelope interceptors (before)."); skipMessagingContainer |= true; } // execute the messages in the message envelope if (! skipMessagingContainer) { Iterator<Message> messages = messageEnvelope.getMessages(); while (messages.hasNext()) { Message message = messages.next(); this.execute(message, executionContext, executionResult); } } // execute message envelope interceptors (after) InterceptorExecutor.executeMessageEnvelopeInterceptorsAfter(this.getInterceptors(), messageEnvelope, executionContext, executionResult); // after message envelope this.after(messageEnvelope, executionContext, executionResult); } catch (Throwable ex) { // process exception ex = executionContext.processException(ex); // execute message envelope interceptors (exception) try { InterceptorExecutor.executeMessageEnvelopeInterceptorsException(this.getInterceptors(), messageEnvelope, executionContext, executionResult, (Xdi2MessagingException) ex); } catch (Exception ex2) { log.warn("Error while messaging envelope interceptor tried to handle exception: " + ex2.getMessage(), ex2); } // exception in message envelope try { this.exception(messageEnvelope, executionContext, executionResult, (Xdi2MessagingException) ex); } catch (Exception ex2) { log.warn("Error while messaging envelope target tried to handle exception: " + ex2.getMessage(), ex2); } // add exception to execution result executionResult.addException(ex); // re-throw it throw (Xdi2MessagingException) ex; } finally { this.getInterceptors().clearDisabledForMessageEnvelope(messageEnvelope); this.getContributors().clearDisabledForMessageEnvelope(messageEnvelope); // pop try { executionContext.popMessageEnvelope(); } catch (Exception ex) { log.warn("Error while popping message envelope: " + ex.getMessage(), ex); } // finish execution result try { executionResult.finish(); } catch (Exception ex) { log.warn("Error while finishing execution context: " + ex.getMessage(), ex); } // execute result interceptors (finish) try { InterceptorExecutor.executeResultInterceptorsFinish(this.getInterceptors(), this, executionContext, executionResult); } catch (Exception ex) { log.warn("Error while execution result interceptors: " + ex.getMessage(), ex); } // pop try { executionContext.popMessagingContainer(); } catch (Exception ex) { log.warn("Error while popping messaging container: " + ex.getMessage(), ex); } // done if (log.isDebugEnabled()) log.debug("" + this.getClass().getSimpleName() + " finished execution. Trace: " + executionContext.getTraceBlock()); } } /** * Executes a message by executing all its operations. * @param message The XDI message containing XDI operations to be executed. * @param executionContext An "execution context" object that carries state between * messaging containers, interceptors and contributors. * @param executionResult The execution result produced by executing the messaging request. */ public void execute(Message message, ExecutionContext executionContext, ExecutionResult executionResult) throws Xdi2MessagingException { if (message == null) throw new NullPointerException(); if (executionContext == null) throw new NullPointerException(); if (executionResult == null) throw new NullPointerException(); if (log.isDebugEnabled()) log.debug(this.getClass().getSimpleName() + ": Executing message (" + message.getContextNode().getXDIAddress().toString() + ") (" + message.getOperationCount() + " operations)."); boolean skipMessagingContainer = false; try { // push executionContext.pushMessage(message, message.getContextNode().getXDIAddress().toString()); // reset execution context executionContext.resetMessageAttributes(); // before message skipMessagingContainer |= this.before(message, executionContext, executionResult); // execute message interceptors (before) InterceptorResult interceptorResultBefore = InterceptorExecutor.executeMessageInterceptorsBefore(this.getInterceptors(), message, executionContext, executionResult); if (interceptorResultBefore.isSkipMessagingContainer()) { if (log.isDebugEnabled()) log.debug("Skipping messaging container according to message interceptors (before)."); skipMessagingContainer |= true; } // execute the operations in the message if (! skipMessagingContainer) { Iterator<Operation> operations = message.getOperations(); while (operations.hasNext()) { Operation operation = operations.next(); this.execute(operation, executionContext, executionResult); } } // execute message interceptors (after) InterceptorExecutor.executeMessageInterceptorsAfter(this.getInterceptors(), message, executionContext, executionResult); // after message this.after(message, executionContext, executionResult); } catch (Exception ex) { // process exception and re-throw it throw executionContext.processException(ex); } finally { this.getInterceptors().clearDisabledForMessage(message); this.getContributors().clearDisabledForMessage(message); // pop try { executionContext.popMessage(); } catch (Exception ex) { log.warn("Error while popping message: " + ex.getMessage(), ex); } } } /** * Executes an operation. * @param operation The XDI operation. * @param executionContext An "execution context" object that carries state between * messaging containers, interceptors and contributors. * @param executionResult The execution result produced by executing the messaging request. */ public void execute(Operation operation, ExecutionContext executionContext, ExecutionResult executionResult) throws Xdi2MessagingException { Graph operationResultGraph = executionResult.createOperationResultGraph(operation); this.execute(operation, operationResultGraph, executionContext); } /** * Executes an operation. * @param operation The XDI operation. * @param operationResultGraph The graph for result statements from this operation. * @param executionResult The execution result produced by executing the messaging request. */ public void execute(Operation operation, Graph operationResultGraph, ExecutionContext executionContext) throws Xdi2MessagingException { if (operation == null) throw new NullPointerException(); if (executionContext == null) throw new NullPointerException(); if (log.isDebugEnabled()) log.debug(this.getClass().getSimpleName() + ": Executing operation (" + operation.getOperationXDIAddress() + ")."); boolean skipMessagingContainer = false; try { // push executionContext.pushOperation(operation, operation.getOperationXDIAddress().toString()); // reset execution context executionContext.resetOperationAttributes(); // before operation skipMessagingContainer |= this.before(operation, operationResultGraph, executionContext); // execute operation interceptors (before) InterceptorResult interceptorResultBefore = InterceptorExecutor.executeOperationInterceptorsBefore(this.getInterceptors(), operation, operationResultGraph, executionContext); if (interceptorResultBefore.isSkipMessagingContainer()) { if (log.isDebugEnabled()) log.debug("Skipping messaging container according to operation interceptors (before)."); skipMessagingContainer |= true; } // execute the target address or statements in the operation if (! skipMessagingContainer) { XDIAddress targetXDIAddress = operation.getTargetXDIAddress(); IterableIterator<XDIStatement> targetXDIStatements = operation.getTargetXDIStatements(); if (targetXDIAddress != null) { this.execute(targetXDIAddress, operation, operationResultGraph, executionContext); } if (targetXDIStatements != null) { for (XDIStatement targetXDIStatement : targetXDIStatements) { this.execute(targetXDIStatement, operation, operationResultGraph, executionContext); } } } // execute operation interceptors (after) InterceptorExecutor.executeOperationInterceptorsAfter(this.getInterceptors(), operation, operationResultGraph, executionContext); // after operation this.after(operation, operationResultGraph, executionContext); } catch (Exception ex) { // process exception and re-throw it throw executionContext.processException(ex); } finally { this.getInterceptors().clearDisabledForOperation(operation); this.getContributors().clearDisabledForOperation(operation); // pop try { executionContext.popOperation(); } catch (Exception ex) { log.warn("Error while popping operation: " + ex.getMessage(), ex); } } } /** * Executes a target address. * @param targetXDIAddress The target address. * @param operation The XDI operation. * @param operationResultGraph The operation's result graph. * @param executionContext An "execution context" object that carries state between * messaging containers, interceptors and contributors. */ public void execute(XDIAddress targetXDIAddress, Operation operation, Graph operationResultGraph, ExecutionContext executionContext) throws Xdi2MessagingException { if (targetXDIAddress == null) throw new NullPointerException(); if (operation == null) throw new NullPointerException(); if (operationResultGraph == null) throw new NullPointerException(); if (executionContext == null) throw new NullPointerException(); try { // push executionContext.pushTargetAddress(targetXDIAddress, "" + targetXDIAddress); // execute target interceptors (address) if ((targetXDIAddress = InterceptorExecutor.executeTargetInterceptorsAddress(this.getInterceptors(), targetXDIAddress, operation, operationResultGraph, executionContext)) == null) { if (log.isDebugEnabled()) log.debug("Skipping messaging container according to target interceptors (address)."); return; } // execute contributors (address) ContributorResult contributorResultAddress = ContributorExecutor.executeContributorsAddress(this.getContributors(), new XDIAddress[0], XDIConstants.XDI_ADD_ROOT, targetXDIAddress, operation, operationResultGraph, executionContext); if (contributorResultAddress.isSkipMessagingContainer()) { if (log.isDebugEnabled()) log.debug("Skipping messaging container according to contributors (address)."); return; } // get an address handler, and execute on the address AddressHandler addressHandler = this.getAddressHandler(targetXDIAddress); if (addressHandler == null) { if (log.isDebugEnabled()) log.debug(this.getClass().getSimpleName() + ": No address handler for target address " + targetXDIAddress + "."); return; } if (log.isDebugEnabled()) log.debug(this.getClass().getSimpleName() + ": Executing " + operation.getOperationXDIAddress() + " on target address " + targetXDIAddress + " (" + addressHandler.getClass().getName() + ")."); addressHandler.executeOnAddress(targetXDIAddress, operation, operationResultGraph, executionContext); } catch (Exception ex) { // process exception and re-throw it throw executionContext.processException(ex); } finally { // pop try { executionContext.popTargetAddress(); } catch (Exception ex) { log.warn("Error while popping target address: " + ex.getMessage(), ex); } } } /** * Executes a target statement. * @param targetXDIStatement The target statement. * @param operation The XDI operation. * @param operationResultGraph The operation's result graph. * @param executionContext An "execution context" object that carries state between * messaging containers, interceptors and contributors. */ public void execute(XDIStatement targetXDIStatement, Operation operation, Graph operationResultGraph, ExecutionContext executionContext) throws Xdi2MessagingException { if (targetXDIStatement == null) throw new NullPointerException(); if (operation == null) throw new NullPointerException(); if (operationResultGraph == null) throw new NullPointerException(); if (executionContext == null) throw new NullPointerException(); try { // push executionContext.pushTargetStatement(targetXDIStatement, "" + targetXDIStatement); // execute target interceptors (statement) if ((targetXDIStatement = InterceptorExecutor.executeTargetInterceptorsStatement(this.getInterceptors(), targetXDIStatement, operation, operationResultGraph, executionContext)) == null) { if (log.isDebugEnabled()) log.debug("Skipping messaging container according to target interceptors (statement)."); return; } // execute contributors (statement) ContributorResult contributorResultAddress = ContributorExecutor.executeContributorsStatement(this.getContributors(), new XDIAddress[0], XDIConstants.XDI_ADD_ROOT, targetXDIStatement, operation, operationResultGraph, executionContext); if (contributorResultAddress.isSkipMessagingContainer()) { if (log.isDebugEnabled()) log.debug("Skipping messaging container according to contributors (statement)."); return; } // get a statement handler, and execute on the statement StatementHandler statementHandler = this.getStatementHandler(targetXDIStatement); if (statementHandler == null) { if (log.isDebugEnabled()) log.debug(this.getClass().getSimpleName() + ": No statement handler for target statement " + targetXDIStatement + "."); return; } if (log.isDebugEnabled()) log.debug(this.getClass().getSimpleName() + ": Executing " + operation.getOperationXDIAddress() + " on target statement " + targetXDIStatement + " (" + statementHandler.getClass().getName() + ")."); statementHandler.executeOnStatement(targetXDIStatement, operation, operationResultGraph, executionContext); } catch (Exception ex) { // process exception and re-throw it throw executionContext.processException(ex); } finally { // pop try { executionContext.popTargetStatement(); } catch (Exception ex) { log.warn("Error while popping target statement: " + ex.getMessage(), ex); } } } /* * We can provide a container with a standard set of interceptors and contributors. */ public void addStandardExtensions() { this.interceptors.addInterceptor(new ToInterceptor()); this.interceptors.addInterceptor(new RefInterceptor()); this.interceptors.addInterceptor(new HasInterceptor()); this.interceptors.addInterceptor(new SecretTokenInterceptor()); this.interceptors.addInterceptor(new SignatureInterceptor()); this.interceptors.addInterceptor(new DigestInterceptor()); this.interceptors.addInterceptor(new LinkContractInterceptor()); this.interceptors.addInterceptor(new SigningInterceptor()); this.interceptors.addInterceptor(new ConnectInterceptor()); this.interceptors.addInterceptor(new SendInterceptor()); this.interceptors.addInterceptor(new PushInInterceptor()); this.interceptors.addInterceptor(new PushOutInterceptor()); this.interceptors.addInterceptor(new DeferResultInterceptor()); this.contributors.addContributor(new GenerateDigestSecretTokenContributor()); this.contributors.addContributor(new GenerateKeyContributor()); } /* * These are for being overridden by subclasses */ protected boolean before(MessageEnvelope messageEnvelope, ExecutionContext executionContext, ExecutionResult executionResult) throws Xdi2MessagingException { return false; } protected boolean before(Message message, ExecutionContext executionContext, ExecutionResult executionResult) throws Xdi2MessagingException { return false; } protected boolean before(Operation operation, Graph operationResultGraph, ExecutionContext executionContext) throws Xdi2MessagingException { return false; } protected void after(MessageEnvelope messageEnvelope, ExecutionContext executionContext, ExecutionResult executionResult) throws Xdi2MessagingException { } protected void after(Message message, ExecutionContext executionContext, ExecutionResult executionResult) throws Xdi2MessagingException { } protected void after(Operation operation, Graph operationResultGraph, ExecutionContext executionContext) throws Xdi2MessagingException { } protected void exception(MessageEnvelope messageEnvelope, ExecutionContext executionContext, ExecutionResult executionResult, Xdi2MessagingException ex) throws Xdi2MessagingException { } protected AddressHandler getAddressHandler(XDIAddress targetAddress) throws Xdi2MessagingException { return null; } protected StatementHandler getStatementHandler(XDIStatement targetStatement) throws Xdi2MessagingException { return null; } /* * Getters and setters */ @Override public XDIArc getOwnerPeerRootXDIArc() { return this.ownerPeerRootXDIArc; } @Override public XDIAddress getOwnerXDIAddress() { return this.ownerPeerRootXDIArc == null ? null : XdiPeerRoot.getXDIAddressOfPeerRootXDIArc(this.ownerPeerRootXDIArc); } public void setOwnerPeerRootXDIArc(XDIArc ownerPeerRootXDIArc) { this.ownerPeerRootXDIArc = ownerPeerRootXDIArc; } public void setOwnerXDIAddress(XDIAddress ownerXDIAddress) { this.ownerPeerRootXDIArc = XdiPeerRoot.createPeerRootXDIArc(ownerXDIAddress); } @Override public boolean ownsPeerRootXDIArc(XDIArc peerRootXDIArc) { return peerRootXDIArc.equals(this.getOwnerPeerRootXDIArc()); } public InterceptorList<MessagingContainer> getInterceptors() { return this.interceptors; } public void setInterceptors(InterceptorList<MessagingContainer> interceptors) { this.interceptors = interceptors; } public ContributorMap getContributors() { return this.contributors; } public void setContributors(ContributorMap contributors) { this.contributors = contributors; } }