From f7f3bbbc63a9e177f56064d821dc5f502dee378e Mon Sep 17 00:00:00 2001 From: Erik Brakkee Date: Sat, 23 Jul 2011 22:13:47 +0200 Subject: [PATCH] initial versions. --- common/pom.xml | 24 ++ .../java/org/wamblee/xmlrouter/common/Id.java | 35 ++ config/pom.xml | 43 +++ .../org/wamblee/xmlrouter/config/Config.java | 47 +++ .../xmlrouter/config/DocumentType.java | 27 ++ .../org/wamblee/xmlrouter/config/Filter.java | 13 + .../org/wamblee/xmlrouter/config/Rule.java | 62 ++++ .../xmlrouter/config/Transformation.java | 28 ++ impl/pom.xml | 49 +++ .../org/wamblee/xmlrouter/impl/Constants.java | 5 + .../xmlrouter/impl/RobustDestination.java | 130 +++++++ .../wamblee/xmlrouter/impl/RobustFilter.java | 34 ++ .../xmlrouter/impl/RobustTransformation.java | 94 ++++++ .../xmlrouter/impl/TransformationPath.java | 79 +++++ .../xmlrouter/impl/Transformations.java | 159 +++++++++ .../org/wamblee/xmlrouter/impl/XMLRouter.java | 265 +++++++++++++++ .../xmlrouter/impl/RobustDestinationTest.java | 96 ++++++ .../xmlrouter/impl/RobustFilterTest.java | 49 +++ .../impl/RobustTransformationTest.java | 82 +++++ .../xmlrouter/impl/TransformationsTest.java | 102 ++++++ .../wamblee/xmlrouter/impl/XMLRouterTest.java | 318 ++++++++++++++++++ publish/pom.xml | 34 ++ .../wamblee/xmlrouter/publish/Gateway.java | 37 ++ subscribe/pom.xml | 39 +++ .../xmlrouter/subscribe/Destination.java | 30 ++ .../subscribe/DestinationRegistry.java | 39 +++ .../subscribe/RegistrationException.java | 28 ++ 27 files changed, 1948 insertions(+) create mode 100644 common/pom.xml create mode 100644 common/src/main/java/org/wamblee/xmlrouter/common/Id.java create mode 100644 config/pom.xml create mode 100644 config/src/main/java/org/wamblee/xmlrouter/config/Config.java create mode 100644 config/src/main/java/org/wamblee/xmlrouter/config/DocumentType.java create mode 100644 config/src/main/java/org/wamblee/xmlrouter/config/Filter.java create mode 100644 config/src/main/java/org/wamblee/xmlrouter/config/Rule.java create mode 100644 config/src/main/java/org/wamblee/xmlrouter/config/Transformation.java create mode 100644 impl/pom.xml create mode 100644 impl/src/main/java/org/wamblee/xmlrouter/impl/Constants.java create mode 100644 impl/src/main/java/org/wamblee/xmlrouter/impl/RobustDestination.java create mode 100644 impl/src/main/java/org/wamblee/xmlrouter/impl/RobustFilter.java create mode 100644 impl/src/main/java/org/wamblee/xmlrouter/impl/RobustTransformation.java create mode 100644 impl/src/main/java/org/wamblee/xmlrouter/impl/TransformationPath.java create mode 100644 impl/src/main/java/org/wamblee/xmlrouter/impl/Transformations.java create mode 100644 impl/src/main/java/org/wamblee/xmlrouter/impl/XMLRouter.java create mode 100644 impl/src/test/java/org/wamblee/xmlrouter/impl/RobustDestinationTest.java create mode 100644 impl/src/test/java/org/wamblee/xmlrouter/impl/RobustFilterTest.java create mode 100644 impl/src/test/java/org/wamblee/xmlrouter/impl/RobustTransformationTest.java create mode 100644 impl/src/test/java/org/wamblee/xmlrouter/impl/TransformationsTest.java create mode 100644 impl/src/test/java/org/wamblee/xmlrouter/impl/XMLRouterTest.java create mode 100644 publish/pom.xml create mode 100644 publish/src/main/java/org/wamblee/xmlrouter/publish/Gateway.java create mode 100644 subscribe/pom.xml create mode 100644 subscribe/src/main/java/org/wamblee/xmlrouter/subscribe/Destination.java create mode 100644 subscribe/src/main/java/org/wamblee/xmlrouter/subscribe/DestinationRegistry.java create mode 100644 subscribe/src/main/java/org/wamblee/xmlrouter/subscribe/RegistrationException.java diff --git a/common/pom.xml b/common/pom.xml new file mode 100644 index 0000000..d2c06b5 --- /dev/null +++ b/common/pom.xml @@ -0,0 +1,24 @@ + + + + + org.wamblee.xmlrouter + xmlrouter-root + 0.1-SNAPSHOT + + + 4.0.0 + xmlrouter-common + jar + /xmlrouter/common + http://wamblee.org + + + + + xmlrouter-site + file:${distrib}/cache + + + + diff --git a/common/src/main/java/org/wamblee/xmlrouter/common/Id.java b/common/src/main/java/org/wamblee/xmlrouter/common/Id.java new file mode 100644 index 0000000..504459a --- /dev/null +++ b/common/src/main/java/org/wamblee/xmlrouter/common/Id.java @@ -0,0 +1,35 @@ +package org.wamblee.xmlrouter.common; + +public class Id { + + private int id; + + public Id(int aId) { + id = aId; + } + + public int getId() { + return id; + } + + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object aObj) { + if (aObj == null) { + return false; + } + if (!(aObj instanceof Id)) { + return false; + } + return id == ((Id) aObj).id; + } + + @Override + public String toString() { + return id + ""; + } +} diff --git a/config/pom.xml b/config/pom.xml new file mode 100644 index 0000000..26fa8b2 --- /dev/null +++ b/config/pom.xml @@ -0,0 +1,43 @@ + + + + + org.wamblee.xmlrouter + xmlrouter-root + 0.1-SNAPSHOT + + + 4.0.0 + xmlrouter-config + jar + /xmlrouter/config + http://wamblee.org + + + + org.wamblee.xmlrouter + xmlrouter-common + 0.1-SNAPSHOT + + + org.wamblee + wamblee-support-general + + + junit + junit + + + org.mockito + mockito-all + + + + + + xmlrouter-site + file:${distrib}/cache + + + + diff --git a/config/src/main/java/org/wamblee/xmlrouter/config/Config.java b/config/src/main/java/org/wamblee/xmlrouter/config/Config.java new file mode 100644 index 0000000..940b346 --- /dev/null +++ b/config/src/main/java/org/wamblee/xmlrouter/config/Config.java @@ -0,0 +1,47 @@ +/* + * Copyright 2005-2011 the original author or authors. + * + * 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.wamblee.xmlrouter.config; + +import java.util.Collection; + +import org.wamblee.xmlrouter.common.Id; + +/** + * Configuration API for the XML router. + * + * @author Erik Brakkee + */ +public interface Config { + + Id addDocumentType(DocumentType aType); + + void removeDocumentType(Id aId); + + Collection getDocumentTypes(); + + Id addTransformation(Transformation aTransformation); + + void removeTransformation(Id aId); + + Collection getTransformations(); + + Id addFilter(Filter aFilter); + + void removeFilter(Id aId); + + Collection getFilters(); + +} diff --git a/config/src/main/java/org/wamblee/xmlrouter/config/DocumentType.java b/config/src/main/java/org/wamblee/xmlrouter/config/DocumentType.java new file mode 100644 index 0000000..edcf294 --- /dev/null +++ b/config/src/main/java/org/wamblee/xmlrouter/config/DocumentType.java @@ -0,0 +1,27 @@ +package org.wamblee.xmlrouter.config; + +import javax.xml.transform.dom.DOMSource; + +public interface DocumentType { + + /** + * Symbolic name for the document type. + * @return Name. + */ + String getName(); + + /** + * Checks if a document is of the given type. + * @param aSource Document + * @return True iff the document is of the given type. + */ + boolean isInstance(DOMSource aSource); + + /** + * Validates the document. Implementations that do not validate should simply + * return true always. + * @param aSource Document. + * @return True iff the document is valid. + */ + boolean validate(DOMSource aSource); +} diff --git a/config/src/main/java/org/wamblee/xmlrouter/config/Filter.java b/config/src/main/java/org/wamblee/xmlrouter/config/Filter.java new file mode 100644 index 0000000..db9dee9 --- /dev/null +++ b/config/src/main/java/org/wamblee/xmlrouter/config/Filter.java @@ -0,0 +1,13 @@ +package org.wamblee.xmlrouter.config; + +import javax.xml.transform.dom.DOMSource; + +public interface Filter { + + /** + * Determines if a given document will be processed or not. + * @param aSource Source document. + * @return + */ + boolean isAllowed(String aDocumentType, DOMSource aSource); +} diff --git a/config/src/main/java/org/wamblee/xmlrouter/config/Rule.java b/config/src/main/java/org/wamblee/xmlrouter/config/Rule.java new file mode 100644 index 0000000..e8bbfb4 --- /dev/null +++ b/config/src/main/java/org/wamblee/xmlrouter/config/Rule.java @@ -0,0 +1,62 @@ +/* + * Copyright 2005-2011 the original author or authors. + * + * 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.wamblee.xmlrouter.config; + +import org.wamblee.xml.XMLDocument; +import org.wamblee.xml.XMLException; +import org.wamblee.xml.XMLProcessor; +import org.wamblee.xml.XPathExpression; + +/** + * Basic rule for XML routing. + * + * @author Erik Brakkee + */ +public class Rule { + + private XPathExpression condition; + private XMLProcessor processor; + private String destination; + + /** + * Constructs the rule. + * @param aCondition Condition to be satisfied. + * @param aProcessor XML Processor to be applied when the condition is valid. + * @param aDestination Destination for the result of this rule. + */ + public Rule(XPathExpression aCondition, XMLProcessor aProcessor, String aDestination) { + condition = aCondition; + processor = aProcessor; + destination = aDestination; + } + + /** + * Applies the rule to a document. + * @param aDocument Document to apply rule ot. + * @return Transformed document or null if the rule did not apply. + * @throws XMLException In case the application of the rule gives an error. + */ + public XMLDocument apply(XMLDocument aDocument) throws XMLException { + if ( !condition.booleanEval(aDocument)) { + return null; + } + return aDocument.process(processor); + } + + public String getDestination() { + return destination; + } +} diff --git a/config/src/main/java/org/wamblee/xmlrouter/config/Transformation.java b/config/src/main/java/org/wamblee/xmlrouter/config/Transformation.java new file mode 100644 index 0000000..b681afc --- /dev/null +++ b/config/src/main/java/org/wamblee/xmlrouter/config/Transformation.java @@ -0,0 +1,28 @@ +package org.wamblee.xmlrouter.config; + +import javax.xml.transform.dom.DOMSource; + +public interface Transformation { + + /** + * From type that can be transformed. + * + * @return From document type. + */ + String getFromType(); + + /** + * To document type. + * + * @return Document type. + */ + String getToType(); + + /** + * @param aDocument + * Document to transform. + * @return Resulting document. + */ + DOMSource transform(DOMSource aDocument); + +} diff --git a/impl/pom.xml b/impl/pom.xml new file mode 100644 index 0000000..a20b1e4 --- /dev/null +++ b/impl/pom.xml @@ -0,0 +1,49 @@ + + + + + org.wamblee.xmlrouter + xmlrouter-root + 0.1-SNAPSHOT + + + 4.0.0 + xmlrouter-impl + jar + /xmlrouter/impl + http://wamblee.org + + + + org.wamblee.xmlrouter + xmlrouter-config + 0.1-SNAPSHOT + + + org.wamblee.xmlrouter + xmlrouter-publish + 0.1-SNAPSHOT + + + org.wamblee.xmlrouter + xmlrouter-subscribe + 0.1-SNAPSHOT + + + junit + junit + + + org.mockito + mockito-all + + + + + + xmlrouter-site + file:${distrib}/cache + + + + diff --git a/impl/src/main/java/org/wamblee/xmlrouter/impl/Constants.java b/impl/src/main/java/org/wamblee/xmlrouter/impl/Constants.java new file mode 100644 index 0000000..057c90f --- /dev/null +++ b/impl/src/main/java/org/wamblee/xmlrouter/impl/Constants.java @@ -0,0 +1,5 @@ +package org.wamblee.xmlrouter.impl; + +public enum Constants { + UNKNOWN_DOCUMENT_TYPE, UNKNOWN_DESTINATION_NAME +} diff --git a/impl/src/main/java/org/wamblee/xmlrouter/impl/RobustDestination.java b/impl/src/main/java/org/wamblee/xmlrouter/impl/RobustDestination.java new file mode 100644 index 0000000..6de2c19 --- /dev/null +++ b/impl/src/main/java/org/wamblee/xmlrouter/impl/RobustDestination.java @@ -0,0 +1,130 @@ +package org.wamblee.xmlrouter.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.xml.transform.dom.DOMSource; + +import org.wamblee.xml.XMLDocument; +import org.wamblee.xmlrouter.common.Id; +import org.wamblee.xmlrouter.subscribe.Destination; + +/** + * Provides robustness for the use of externally provided destinations by + * catching null return values and exceptions and providing usable default + * return values. + * + * @author Erik Brakkee + * + */ +public class RobustDestination implements Destination { + + private static final Logger LOGGER = Logger + .getLogger(RobustDestination.class.getName()); + + private Id id; + private Destination destination; + + public RobustDestination(Id aId, Destination aDestination) { + id = aId; + destination = aDestination; + } + + @Override + public String getName() { + try { + String res = destination.getName(); + if (res == null) { + logNameReturnedNull(); + return Constants.UNKNOWN_DESTINATION_NAME.toString(); + } + return res; + } catch (Exception e) { + logNameThrewException(e); + return Constants.UNKNOWN_DESTINATION_NAME.toString(); + } + } + + @Override + public Collection chooseFromTargetTypes( + Collection aPossibleTargetTypes) { + try { + Collection res = destination + .chooseFromTargetTypes(aPossibleTargetTypes); + if (res == null) { + logChooseFromTargetTypesReturnedNull(aPossibleTargetTypes); + return Collections.EMPTY_LIST; + } + Collection finalRes = new ArrayList(); + for (String type : res) { + if (!aPossibleTargetTypes.contains(type)) { + logChooseFromTargetTypesReturnedInvalidType( + aPossibleTargetTypes, type); + } else { + finalRes.add(type); + } + } + return finalRes; + } catch (Exception e) { + logChooseFromTargetTypesThrewException(aPossibleTargetTypes, e); + return Collections.EMPTY_LIST; + } + } + + @Override + public boolean receive(DOMSource aEvent) { + try { + return destination.receive(aEvent); + } catch (Exception e) { + logReceiveThrewException(aEvent, e); + return false; + } + } + + private void logNameThrewException(Exception aE) { + LOGGER.log(Level.WARNING, + "getName() threw exception for destination id " + id + + " returning name " + Constants.UNKNOWN_DESTINATION_NAME + + " instead", aE); + } + + private void logNameReturnedNull() { + LOGGER.log(Level.WARNING, + "getName() returned null for destination id " + id + + " returning name " + Constants.UNKNOWN_DESTINATION_NAME + + " instead"); + } + + private void logChooseFromTargetTypesReturnedInvalidType( + Collection aPossibleTargetTypes, String aType) { + LOGGER + .log( + Level.WARNING, + "chooseFromTargetTypes() for destination {0} returned invalid type ''{1}'' which is not in the offered set of types {2}", + new Object[] { id, aType, aPossibleTargetTypes.toString() }); + } + + private void logChooseFromTargetTypesReturnedNull( + Collection aPossibleTargetTypes) { + LOGGER.log(Level.WARNING, + "chooseFromTargetTypes() returned null for destination " + id); + } + + private void logChooseFromTargetTypesThrewException( + Collection aPossibleTargetTypes, Exception aE) { + LOGGER + .log( + Level.WARNING, + "chooseFromTargetTypes() threw exception for destination " + id, + aE); + } + + private void logReceiveThrewException(DOMSource aEvent, Exception aE) { + LOGGER.log(Level.WARNING, "Receive threw exception for destination " + + id + " for event " + new XMLDocument(aEvent).print(true), aE); + } + +} diff --git a/impl/src/main/java/org/wamblee/xmlrouter/impl/RobustFilter.java b/impl/src/main/java/org/wamblee/xmlrouter/impl/RobustFilter.java new file mode 100644 index 0000000..413ae92 --- /dev/null +++ b/impl/src/main/java/org/wamblee/xmlrouter/impl/RobustFilter.java @@ -0,0 +1,34 @@ +package org.wamblee.xmlrouter.impl; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.xml.transform.dom.DOMSource; + +import org.wamblee.xmlrouter.common.Id; +import org.wamblee.xmlrouter.config.Filter; + +public class RobustFilter implements Filter { + + private static final Logger LOGGER = Logger.getLogger(RobustFilter.class + .getName()); + + private Id id; + private Filter filter; + + public RobustFilter(Id aId, Filter aFilter) { + id = aId; + filter = aFilter; + } + + @Override + public boolean isAllowed(String aDocumentType, DOMSource aSource) { + try { + return filter.isAllowed(aDocumentType, aSource); + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Filter " + id + + " threw exception, assuming filter returns true", e); + return true; + } + } +} diff --git a/impl/src/main/java/org/wamblee/xmlrouter/impl/RobustTransformation.java b/impl/src/main/java/org/wamblee/xmlrouter/impl/RobustTransformation.java new file mode 100644 index 0000000..fb028ae --- /dev/null +++ b/impl/src/main/java/org/wamblee/xmlrouter/impl/RobustTransformation.java @@ -0,0 +1,94 @@ +package org.wamblee.xmlrouter.impl; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.xml.transform.dom.DOMSource; + +import org.wamblee.xml.XMLDocument; +import org.wamblee.xmlrouter.common.Id; +import org.wamblee.xmlrouter.config.Transformation; + +public class RobustTransformation implements Transformation { + + private static final Logger LOGGER = Logger + .getLogger(RobustTransformation.class.getName()); + + private Id id; + private Transformation transformation; + + public RobustTransformation(Id aId, + Transformation aTransformation) { + id = aId; + transformation = aTransformation; + } + + @Override + public String getFromType() { + try { + String from = transformation.getFromType(); + if (from == null) { + logTypeReturnedNull("from"); + return Constants.UNKNOWN_DOCUMENT_TYPE.toString(); + } + return from; + } catch (Exception e) { + logTypeThrewException("from", e); + return Constants.UNKNOWN_DOCUMENT_TYPE.toString(); + } + } + + @Override + public String getToType() { + try { + String to = transformation.getToType(); + if (to == null) { + logTypeReturnedNull("to"); + return Constants.UNKNOWN_DOCUMENT_TYPE.toString(); + } + return to; + } catch (Exception e) { + logTypeThrewException("to", e); + return Constants.UNKNOWN_DOCUMENT_TYPE.toString(); + } + } + + @Override + public DOMSource transform(DOMSource aDocument) { + try { + DOMSource res = transformation.transform(aDocument); + if (res == null) { + logTransformationReturnedNull(aDocument); + return null; + } + return res; + } catch (Exception e) { + logTranformationThrewException(aDocument, e); + return null; + } + } + + private void logTypeThrewException(String aFromTo, Exception aE) { + LOGGER.log(Level.WARNING, "get" + aFromTo + + " threw exception, returning default value " + + Constants.UNKNOWN_DOCUMENT_TYPE, aE); + } + + private void logTypeReturnedNull(String aFromTo) { + LOGGER.log(Level.WARNING, "get" + aFromTo + + " returned null, returning default value " + + Constants.UNKNOWN_DOCUMENT_TYPE); + } + + private void logTranformationThrewException(DOMSource aEvent, Exception aE) { + LOGGER.log(Level.WARNING, + "transformation " + id + " threw exception for event " + + new XMLDocument(aEvent).print(true), aE); + } + + private void logTransformationReturnedNull(DOMSource aEvent) { + LOGGER.log(Level.WARNING, "transformation " + id + + " returned null for event " + new XMLDocument(aEvent).print(true)); + } + +} diff --git a/impl/src/main/java/org/wamblee/xmlrouter/impl/TransformationPath.java b/impl/src/main/java/org/wamblee/xmlrouter/impl/TransformationPath.java new file mode 100644 index 0000000..c054f90 --- /dev/null +++ b/impl/src/main/java/org/wamblee/xmlrouter/impl/TransformationPath.java @@ -0,0 +1,79 @@ +package org.wamblee.xmlrouter.impl; + +import java.util.ArrayList; +import java.util.List; + +import org.wamblee.xmlrouter.config.Transformation; + +public class TransformationPath { + + private List transformations; + + public TransformationPath() { + transformations = new ArrayList(); + } + + public TransformationPath(Transformation aTransformation) { + this(); + transformations.add(aTransformation); + } + + public TransformationPath(List aTransformations) { + this(); + transformations.addAll(aTransformations); + } + + public TransformationPath appendPath(TransformationPath aSequence) { + if (transformations.isEmpty()) { + return new TransformationPath(aSequence.transformations); + } + if (aSequence.transformations.isEmpty()) { + // nothing to append + return new TransformationPath(transformations); + } + // both are non-empty + if (!getToType().equals(aSequence.getFromType())) { + throw new RuntimeException( + "error concatening sequence, destination of first differs from source of second"); + } + List t = new ArrayList(transformations); + t.addAll(aSequence.transformations); + return new TransformationPath(t); + } + + public int size() { + return transformations.size(); + } + + public String getFromType() { + if (transformations.isEmpty()) { + return null; + } + return transformations.get(0).getFromType(); + } + + public String getToType() { + if (transformations.isEmpty()) { + return null; + } + return transformations.get(transformations.size() - 1).getToType(); + } + + public List getTransformations() { + return transformations; + } + + @Override + public String toString() { + StringBuffer buf = new StringBuffer(); + buf.append("TransformationPath("); + for (int i = 0; i < transformations.size(); i++) { + if (i > 0) { + buf.append(", "); + } + buf.append(transformations.get(i).toString()); + } + buf.append(")"); + return buf.toString(); + } +} diff --git a/impl/src/main/java/org/wamblee/xmlrouter/impl/Transformations.java b/impl/src/main/java/org/wamblee/xmlrouter/impl/Transformations.java new file mode 100644 index 0000000..07d086b --- /dev/null +++ b/impl/src/main/java/org/wamblee/xmlrouter/impl/Transformations.java @@ -0,0 +1,159 @@ +package org.wamblee.xmlrouter.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import org.wamblee.xmlrouter.common.Id; +import org.wamblee.xmlrouter.config.Transformation; + +public class Transformations { + + private AtomicInteger sequenceNumber; + private Map transformations; + private List vertices; + private TransformationPath[][] matrix; + + private Map> sequences; + + public Transformations() { + sequenceNumber = new AtomicInteger(1); + transformations = new LinkedHashMap(); + vertices = new ArrayList(); + matrix = new TransformationPath[0][0]; + } + + public Id addTransformation(Transformation aTransformation) { + int seqno = sequenceNumber.getAndIncrement(); + Id id = new Id(seqno); + transformations.put(seqno, + new RobustTransformation(id, aTransformation)); + computeTransformationSequences(); + return id; + } + + public Collection getPossibleTargetTypes(String aType) { + int index = vertices.indexOf(aType); + Set res = new HashSet(); + for (int j = 0; j < vertices.size(); j++) { + if (matrix[index][j] != null) { + String value = matrix[index][j].getToType(); + if (value == null) { + value = aType; + } + res.add(value); + } + } + res.add(aType); + return res; + } + + /** + * Gets the transformation path from A to B. + * + * @param aFrom + * From + * @param aTo + * To + * @return Transformatkon path or null if not found. + */ + public TransformationPath getPath(String aFrom, String aTo) { + int i = vertices.indexOf(aFrom); + if (i == -1) { + if (aFrom.equals(aTo)) { + return new TransformationPath(); + } + return null; + } + + int j = vertices.indexOf(aTo); + return matrix[i][j]; + } + + private void computeTransformationSequences() { + vertices = new ArrayList(); + + // Obtain possible starting points. + Set v = new HashSet(); + for (Transformation transformation : transformations.values()) { + v.add(transformation.getFromType()); + v.add(transformation.getToType()); + } + + vertices.addAll(v); + + matrix = new TransformationPath[vertices.size()][vertices.size()]; + + // Floyd's algorithm. + int nvertices = vertices.size(); + for (int i = 0; i < nvertices; i++) { + matrix[i][i] = new TransformationPath(); + } + for (Transformation transformation : transformations.values()) { + int from = vertices.indexOf(transformation.getFromType()); + int to = vertices.indexOf(transformation.getToType()); + TransformationPath path = new TransformationPath(transformation); + matrix[from][to] = path; + } + + for (int k = 0; k < nvertices; k++) { + for (int i = 0; i < nvertices; i++) { + for (int j = 0; j < nvertices; j++) { + // if the path from i to j through k is shorter then the + // existing path then + // replace it. + int lij = getPathLength(i, j); + int lik = getPathLength(i, k); + int lkj = getPathLength(k, j); + if (lik + lkj < lij) { + matrix[i][j] = matrix[i][k].appendPath(matrix[k][j]); + } + } + } + } + } + + private int getPathLength(int i, int j) { + // We use MAX_INT/3 as infinity. This ensures that the default integer + // comparison in Floyd's algorithm does not lead to overflow. + return matrix[i][j] == null ? Integer.MAX_VALUE / 3 : matrix[i][j] + .size(); + } + + public void removeTransformation(Id aId) { + transformations.remove(aId.getId()); + computeTransformationSequences(); + } + + public Collection getTransformations() { + return Collections.unmodifiableCollection(transformations.values()); + } + + @Override + public String toString() { + StringBuffer buf = new StringBuffer(); + buf.append("Transformations("); + int nvertices = vertices.size(); + for (int i = 0; i < nvertices; i++) { + for (int j = 0; j < nvertices; j++) { + TransformationPath path = matrix[i][j]; + if (path != null) { + buf.append(vertices.get(i)); + buf.append(" -> "); + buf.append(vertices.get(j)); + buf.append(": "); + buf.append(path); + buf.append("\n"); + } + } + } + buf.append(")"); + return buf.toString(); + } +} \ No newline at end of file diff --git a/impl/src/main/java/org/wamblee/xmlrouter/impl/XMLRouter.java b/impl/src/main/java/org/wamblee/xmlrouter/impl/XMLRouter.java new file mode 100644 index 0000000..85a51df --- /dev/null +++ b/impl/src/main/java/org/wamblee/xmlrouter/impl/XMLRouter.java @@ -0,0 +1,265 @@ +package org.wamblee.xmlrouter.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.xml.transform.dom.DOMSource; + +import org.wamblee.xml.XMLDocument; +import org.wamblee.xmlrouter.common.Id; +import org.wamblee.xmlrouter.config.Config; +import org.wamblee.xmlrouter.config.DocumentType; +import org.wamblee.xmlrouter.config.Filter; +import org.wamblee.xmlrouter.config.Transformation; +import org.wamblee.xmlrouter.publish.Gateway; +import org.wamblee.xmlrouter.subscribe.Destination; +import org.wamblee.xmlrouter.subscribe.DestinationRegistry; + +// TODO concurrency. + +public class XMLRouter implements Config, Gateway, DestinationRegistry { + + private static final Logger LOGGER = Logger.getLogger(XMLRouter.class + .getName()); + + private AtomicInteger sequenceNumbers; + private Map documentTypes; + private Transformations transformations; + private Map filters; + private Map destinations; + + public XMLRouter() { + sequenceNumbers = new AtomicInteger(1); + documentTypes = new LinkedHashMap(); + transformations = new Transformations(); + filters = new LinkedHashMap(); + destinations = new LinkedHashMap(); + } + + @Override + public Id addDocumentType(DocumentType aType) { + int seqno = sequenceNumbers.getAndIncrement(); + documentTypes.put(seqno, aType); + return new Id(seqno); + } + + @Override + public void removeDocumentType(Id aId) { + documentTypes.remove(aId); + } + + @Override + public Collection getDocumentTypes() { + return Collections.unmodifiableCollection(documentTypes.values()); + } + + @Override + public Id addTransformation(Transformation aTransformation) { + return transformations.addTransformation(aTransformation); + } + + @Override + public void removeTransformation(Id aId) { + transformations.removeTransformation(aId); + } + + @Override + public Collection getTransformations() { + return transformations.getTransformations(); + } + + @Override + public Id addFilter(Filter aFilter) { + int seqno = sequenceNumbers.getAndIncrement(); + filters.put(seqno, aFilter); + return new Id(seqno); + } + + @Override + public void removeFilter(Id aId) { + filters.remove(aId); + } + + @Override + public Collection getFilters() { + return Collections.unmodifiableCollection(filters.values()); + } + + @Override + public boolean publish(String aSource, DOMSource aEvent) { + + boolean delivered = false; + try { + + List filteredInputTypes = determineFilteredInputTypes(aEvent); + if (filteredInputTypes.isEmpty()) { + if (LOGGER.isLoggable(Level.FINE)) { + String doc = new XMLDocument(aEvent).print(true); + LOGGER + .log( + Level.FINE, + "Event ''0}'' from source {1} removed because of filters.", + new Object[] { doc, aSource }); + } + return delivered; + } + + // get the reachable target types through transformations. + + // It is possible that a given event belongs to multiple input + // types. + // This is however certainly not the main case. + + for (String inputType : filteredInputTypes) { + boolean result = deliverEvent(aSource, aEvent, inputType); + delivered = delivered || result; + } + } finally { + if (!delivered) { + destinationNotFound(aSource, aEvent); + } + return delivered; + } + } + + private boolean deliverEvent(String aSource, DOMSource aEvent, + String aInputType) { + + boolean delivered = false; + Set possibleTargetTypes = new HashSet(); + possibleTargetTypes.addAll(transformations + .getPossibleTargetTypes(aInputType)); + + // ask each destination what target types, if any they want to have. + for (Destination destination : destinations.values()) { + Collection requested = destination + .chooseFromTargetTypes(possibleTargetTypes); + if (!requested.isEmpty()) { + // Deliver to the destination. + for (String targetType : requested) { + TransformationPath path = transformations.getPath( + aInputType, targetType); + List ts = path.getTransformations(); + int i = 0; + boolean allowed = true; + DOMSource transformed = aEvent; + while (i < ts.size() && allowed && transformed != null) { + Transformation t = ts.get(i); + DOMSource orig = transformed; + transformed = t.transform(transformed); + if (transformed == null) { + transformationReturnedNull(aSource, aEvent, + aInputType, t, orig); + } + + if (!isAllowedByFilters(t.getToType(), transformed)) { + allowed = false; + } + i++; + } + if (allowed && transformed != null) { + // all transformations done and all filters still + // allow the event. + boolean result = destination.receive(transformed); + delivered = delivered || result; + + } + } + } + } + return delivered; + } + + private List determineFilteredInputTypes(DOMSource aEvent) { + List types = determineDocumentTypes(aEvent); + // apply filters to the input + List filteredTypes = new ArrayList(); + for (String type : types) { + boolean allowed = isAllowedByFilters(type, aEvent); + if (allowed) { + filteredTypes.add(type); + } + } + return filteredTypes; + } + + private boolean isAllowedByFilters(String aType, DOMSource aEvent) { + boolean allowed = true; + for (Filter filter : filters.values()) { + if (!filter.isAllowed(aType, aEvent)) { + allowed = false; + } + } + return allowed; + } + + private List determineDocumentTypes(DOMSource aEvent) { + List res = new ArrayList(); + for (DocumentType type : documentTypes.values()) { + if (type.isInstance(aEvent)) { + res.add(type.getName()); + } + } + return res; + } + + private void logEvent(String aMessage, String aSource, DOMSource aEvent, + Exception aException) { + LOGGER.log(Level.WARNING, aMessage + ": source '" + aSource + + "': Event: '" + new XMLDocument(aEvent).print(true) + "'", + aException); + } + + private void logEvent(String aMessage, String aSource, DOMSource aEvent) { + LOGGER.log(Level.WARNING, + aMessage + ": " + eventToString(aSource, aEvent)); + } + + private String eventToString(String aSource, DOMSource aEvent) { + return "source '" + aSource + "': Event: '" + + new XMLDocument(aEvent).print(true) + "'"; + } + + private void transformationReturnedNull(String aSource, DOMSource aEvent, + String aInputType, Transformation aT, DOMSource aTransformed) { + LOGGER.log(Level.WARNING, "Transformation returned null for event " + + eventToString(aSource, aEvent) + " inputType '" + aInputType + + "', transformation '" + aT + "' document to transform " + + new XMLDocument(aTransformed).print(true)); + } + + private void destinationNotFound(String aSource, DOMSource aEvent) { + LOGGER.log(Level.WARNING, "No destination found for event: " + + eventToString(aSource, aEvent)); + } + + @Override + public Id registerDestination(Destination aDestination) { + notNull("destination", aDestination); + int seqno = sequenceNumbers.getAndIncrement(); + Id id = new Id(seqno); + destinations.put(seqno, new RobustDestination(id, aDestination)); + return id; + } + + @Override + public void unregisterDestination(Id aId) { + destinations.remove(aId.getId()); + } + + private void notNull(String aName, Object aValue) { + if (aValue == null) { + throw new IllegalArgumentException("Parameter '" + aName + + "' may not be null"); + } + } +} diff --git a/impl/src/test/java/org/wamblee/xmlrouter/impl/RobustDestinationTest.java b/impl/src/test/java/org/wamblee/xmlrouter/impl/RobustDestinationTest.java new file mode 100644 index 0000000..397f7b2 --- /dev/null +++ b/impl/src/test/java/org/wamblee/xmlrouter/impl/RobustDestinationTest.java @@ -0,0 +1,96 @@ +package org.wamblee.xmlrouter.impl; + +import static junit.framework.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import javax.xml.transform.dom.DOMSource; + +import org.junit.Before; +import org.junit.Test; +import org.wamblee.xmlrouter.common.Id; +import org.wamblee.xmlrouter.subscribe.Destination; + +public class RobustDestinationTest { + + private Destination destination; + private Destination robust; + private DOMSource source; + + @Before + public void setUp() { + destination = mock(Destination.class); + robust = new RobustDestination(new Id(100), destination); + source = mock(DOMSource.class); + } + + @Test + public void testNormalFlow() { + when(destination.getName()).thenReturn("name"); + when( + destination.chooseFromTargetTypes((Collection) anyObject())) + .thenReturn(Arrays.asList("x")); + + assertEquals("name", robust.getName()); + assertEquals(Arrays.asList("x"), + robust.chooseFromTargetTypes(Arrays.asList("x", "y"))); + when(destination.receive(same(source))).thenReturn(true); + assertTrue(robust.receive(source)); + verify(destination).receive(same(source)); + + when(destination.receive(same(source))).thenReturn(false); + assertFalse(robust.receive(source)); + + } + + @Test + public void testNameReturnsNull() { + when(destination.getName()).thenReturn(null); + assertEquals(Constants.UNKNOWN_DESTINATION_NAME.toString(), + robust.getName()); + } + + @Test + public void testNameThrowsException() { + doThrow(new RuntimeException("x")).when(destination).getName(); + assertEquals(Constants.UNKNOWN_DESTINATION_NAME.toString(), + robust.getName()); + } + + @Test + public void testChooseFromTargetTypesReturnsNull() { + when( + destination.chooseFromTargetTypes((Collection) anyObject())) + .thenReturn(null); + assertEquals(Collections.EMPTY_LIST, + robust.chooseFromTargetTypes(Arrays.asList("x"))); + } + + @Test + public void testChooseFromTargetTypesThrowsException() { + doThrow(new RuntimeException("x")).when(destination) + .chooseFromTargetTypes(anyList()); + assertEquals(Collections.EMPTY_LIST, + robust.chooseFromTargetTypes(Arrays.asList("x"))); + } + + @Test + public void testChooseFromTargetTypesReturnsInvalidType() { + when( + destination.chooseFromTargetTypes((Collection) anyObject())) + .thenReturn(Arrays.asList("x", "y")); + assertEquals(Arrays.asList("y"), + robust.chooseFromTargetTypes(Arrays.asList("y", "z"))); + } + + @Test + public void testReceiveThrowsException() { + doThrow(new RuntimeException("x")).when(destination).receive( + any(DOMSource.class)); + assertEquals(false, robust.receive(source)); + } +} diff --git a/impl/src/test/java/org/wamblee/xmlrouter/impl/RobustFilterTest.java b/impl/src/test/java/org/wamblee/xmlrouter/impl/RobustFilterTest.java new file mode 100644 index 0000000..7c10282 --- /dev/null +++ b/impl/src/test/java/org/wamblee/xmlrouter/impl/RobustFilterTest.java @@ -0,0 +1,49 @@ +package org.wamblee.xmlrouter.impl; + +import static junit.framework.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import javax.xml.transform.dom.DOMSource; + +import org.junit.Before; +import org.junit.Test; +import org.wamblee.xmlrouter.common.Id; +import org.wamblee.xmlrouter.config.Filter; + +public class RobustFilterTest { + + private Filter filter; + private Filter robust; + private DOMSource source; + + @Before + public void setUp() { + filter = mock(Filter.class); + robust = new RobustFilter(new Id(10), filter); + source = mock(DOMSource.class); + } + + @Test + public void testNoEzception() { + when(filter.isAllowed(anyString(), any(DOMSource.class))).thenReturn( + true); + assertTrue(robust.isAllowed("xx", source)); + verify(filter).isAllowed(eq("xx"), same(source)); + + reset(filter); + when(filter.isAllowed(anyString(), any(DOMSource.class))).thenReturn( + false); + assertFalse(robust.isAllowed("xx", source)); + verify(filter).isAllowed(eq("xx"), same(source)); + } + + @Test + public void testException() { + doThrow(new RuntimeException("bla")).when(filter).isAllowed( + anyString(), any(DOMSource.class)); + assertTrue(robust.isAllowed("xx", source)); + verify(filter).isAllowed(eq("xx"), same(source)); + + } +} diff --git a/impl/src/test/java/org/wamblee/xmlrouter/impl/RobustTransformationTest.java b/impl/src/test/java/org/wamblee/xmlrouter/impl/RobustTransformationTest.java new file mode 100644 index 0000000..ea76c54 --- /dev/null +++ b/impl/src/test/java/org/wamblee/xmlrouter/impl/RobustTransformationTest.java @@ -0,0 +1,82 @@ +package org.wamblee.xmlrouter.impl; + +import static junit.framework.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import javax.xml.transform.dom.DOMSource; + +import org.junit.Before; +import org.junit.Test; +import org.wamblee.xmlrouter.common.Id; +import org.wamblee.xmlrouter.config.Transformation; + +public class RobustTransformationTest { + private Transformation transformation; + private Transformation robust; + private DOMSource source; + private DOMSource resSource; + + @Before + public void setUp() { + transformation = mock(Transformation.class); + robust = new RobustTransformation(new Id(100), + transformation); + source = mock(DOMSource.class); + resSource = mock(DOMSource.class); + } + + @Test + public void testNormalFlow() { + when(transformation.getFromType()).thenReturn("from"); + when(transformation.getToType()).thenReturn("to"); + + when(transformation.transform(same(source))).thenReturn(resSource); + + assertEquals("from", robust.getFromType()); + assertEquals("to", robust.getToType()); + + assertSame(resSource, robust.transform(source)); + } + + @Test + public void testFromReturnsNull() { + when(transformation.getFromType()).thenReturn(null); + assertEquals(Constants.UNKNOWN_DOCUMENT_TYPE.toString(), + robust.getFromType()); + } + + @Test + public void testToReturnsNull() { + when(transformation.getToType()).thenReturn(null); + assertEquals(Constants.UNKNOWN_DOCUMENT_TYPE.toString(), + robust.getToType()); + } + + @Test + public void testFromThrowsException() { + doThrow(new RuntimeException("x")).when(transformation).getFromType(); + assertEquals(Constants.UNKNOWN_DOCUMENT_TYPE.toString(), + robust.getFromType()); + } + + @Test + public void testToThrowsException() { + doThrow(new RuntimeException("x")).when(transformation).getFromType(); + assertEquals(Constants.UNKNOWN_DOCUMENT_TYPE.toString(), + robust.getFromType()); + } + + @Test + public void testTransformReturnsNull() { + when(transformation.transform(any(DOMSource.class))).thenReturn(null); + assertEquals(null, robust.transform(source)); + } + + @Test + public void testTransformationThrowsException() { + doThrow(new RuntimeException("x")).when(transformation).transform( + any(DOMSource.class)); + assertEquals(null, robust.transform(source)); + } +} diff --git a/impl/src/test/java/org/wamblee/xmlrouter/impl/TransformationsTest.java b/impl/src/test/java/org/wamblee/xmlrouter/impl/TransformationsTest.java new file mode 100644 index 0000000..8489f89 --- /dev/null +++ b/impl/src/test/java/org/wamblee/xmlrouter/impl/TransformationsTest.java @@ -0,0 +1,102 @@ +package org.wamblee.xmlrouter.impl; + +import static junit.framework.Assert.*; + +import java.util.Collection; + +import javax.xml.transform.dom.DOMSource; + +import org.junit.Before; +import org.junit.Test; +import org.wamblee.xmlrouter.common.Id; +import org.wamblee.xmlrouter.config.Transformation; + +public class TransformationsTest { + + public static class MyTransformation implements Transformation { + + private String from; + private String to; + + public MyTransformation(String aFrom, String aTo) { + from = aFrom; + to = aTo; + } + + @Override + public String getFromType() { + return from; + } + + @Override + public String getToType() { + return to; + } + + @Override + public DOMSource transform(DOMSource aDocument) { + return null; + } + + @Override + public String toString() { + return from + "->" + to; + } + } + + private Transformations transformations; + + @Before + public void setUp() { + transformations = new Transformations(); + } + + @Test + public void testOneTransformation() { + Id seqno = transformations + .addTransformation(new MyTransformation("A", "B")); + System.out.println(transformations.toString()); + TransformationPath path = transformations.getPath("A", "B"); + assertEquals(1, path.size()); + assertNull(transformations.getPath("B", "A")); + + assertEquals(0, transformations.getPath("A", "A").size()); + assertEquals(0, transformations.getPath("B", "B").size()); + + Collection possibleTargets = transformations + .getPossibleTargetTypes("A"); + assertEquals(2, possibleTargets.size()); + assertTrue(possibleTargets.contains("A")); + assertTrue(possibleTargets.contains("B")); + } + + @Test + public void testMultipleTransformations() { + Id seqno1 = transformations + .addTransformation(new MyTransformation("A", "B")); + Id seqno2 = transformations + .addTransformation(new MyTransformation("B", "C")); + Id seqno3 = transformations + .addTransformation(new MyTransformation("C", "A")); + System.out.println(transformations); + assertEquals(2, transformations.getPath("C", "B").size()); + assertFalse(seqno1.equals(seqno2)); + assertFalse(seqno2.equals(seqno3)); + assertFalse(seqno1.equals(seqno3)); + + transformations.removeTransformation(seqno1); + assertNull(transformations.getPath("C", "B")); + } + + @Test + public void testWithoutTransformations() { + Collection res = transformations.getPossibleTargetTypes("a"); + assertEquals(1, res.size()); + + TransformationPath path = transformations.getPath("A", "A"); + assertNotNull(path); + assertEquals(0, path.size()); + + assertNull(transformations.getPath("B", "C")); + } +} diff --git a/impl/src/test/java/org/wamblee/xmlrouter/impl/XMLRouterTest.java b/impl/src/test/java/org/wamblee/xmlrouter/impl/XMLRouterTest.java new file mode 100644 index 0000000..c4a73ba --- /dev/null +++ b/impl/src/test/java/org/wamblee/xmlrouter/impl/XMLRouterTest.java @@ -0,0 +1,318 @@ +package org.wamblee.xmlrouter.impl; + +import static junit.framework.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.util.Arrays; +import java.util.Collection; + +import javax.xml.transform.dom.DOMSource; + +import org.junit.Before; +import org.junit.Test; +import org.wamblee.xmlrouter.common.Id; +import org.wamblee.xmlrouter.config.DocumentType; +import org.wamblee.xmlrouter.config.Transformation; +import org.wamblee.xmlrouter.subscribe.Destination; + +public class XMLRouterTest { + + public static class MyDestination implements Destination { + + private boolean receiveResult; + private Collection types; + + public MyDestination(boolean aReceiveResult, Collection aTypes) { + receiveResult = aReceiveResult; + types = aTypes; + } + + @Override + public Collection chooseFromTargetTypes( + Collection aPossibleTargetTypes) { + return types; + } + + @Override + public String getName() { + return "xxx"; + } + + @Override + public boolean receive(DOMSource aEvent) { + return receiveResult; + } + } + + private XMLRouter router; + private DOMSource source1; + private DOMSource source2; + private DOMSource source3; + + private Destination destinationSpy; + private Id destinationId; + + @Before + public void setUp() { + router = new XMLRouter(); + source1 = mock(DOMSource.class); + source2 = mock(DOMSource.class); + source3 = mock(DOMSource.class); + } + + @Test + public void testNoInputDocumentsRegistered() { + Destination destination = new MyDestination(true, Arrays.asList("any")); + destinationSpy = spy(destination); + + destinationId = router.registerDestination(destinationSpy); + assertFalse(router.publish("any", source1)); + } + + @Test + public void testOneDestinationNoTransformationSuccess() { + destinationSpy = registerDestination(true, "any"); + registerDocumentType("any"); + + assertTrue(router.publish("any", source1)); + verify(destinationSpy).receive(same(source1)); + + // Unregister the destination. + router.unregisterDestination(destinationId); + reset(destinationSpy); + assertFalse(router.publish("any", source2)); + verifyNoMoreInteractions(destinationSpy); + } + + private void registerDocumentType(String aType) { + DocumentType type = mock(DocumentType.class); + when(type.isInstance(any(DOMSource.class))).thenReturn(true); + when(type.getName()).thenReturn(aType); + Id typeId = router.addDocumentType(type); + } + + private void registerDocumentType(String aType, DOMSource aSource) { + DocumentType type = mock(DocumentType.class); + when(type.isInstance(same(aSource))).thenReturn(true); + when(type.getName()).thenReturn(aType); + Id typeId = router.addDocumentType(type); + } + + private Destination registerDestination(boolean aResult, String... types) { + Destination destination = new MyDestination(aResult, + Arrays.asList(types)); + Destination myspy = spy(destination); + destinationId = router.registerDestination(myspy); + return myspy; + } + + @Test + public void testOneDestinationNotMatches() { + destinationSpy = registerDestination(true); + registerDocumentType("any"); + + assertFalse(router.publish("any", source1)); + verify(destinationSpy, never()).receive(any(DOMSource.class)); + } + + @Test + public void testOneDestinationThrowsException() { + destinationSpy = registerDestination(true, "any"); + registerDocumentType("any"); + + doThrow(new RuntimeException()).when(destinationSpy).receive( + any(DOMSource.class)); + + assertFalse(router.publish("any", source1)); + verify(destinationSpy).receive(same(source1)); + } + + @Test + public void testOneDestinationThrowsExceptionSecondDestinationStillHandled() { + testOneDestinationThrowsException(); + Destination destination2 = new MyDestination(true, Arrays.asList("any")); + Destination destinationSpy2 = spy(destination2); + Id destinationId2 = router + .registerDestination(destinationSpy2); + + assertTrue(router.publish("any", source1)); + verify(destinationSpy2).receive(same(source1)); + } + + @Test + public void testDestinationChooseFromTargetTypesThrowsException() { + destinationSpy = registerDestination(true, "any"); + registerDocumentType("any"); + + doThrow(new RuntimeException()).when(destinationSpy) + .chooseFromTargetTypes((Collection) anyObject()); + + assertFalse(router.publish("any", source1)); + verify(destinationSpy, never()).receive(same(source1)); + } + + @Test + public void testDestinationChooseFromTargetTypesThrowsExceptionSecondDestinationStillOk() { + testDestinationChooseFromTargetTypesThrowsException(); + + Destination destination2 = new MyDestination(true, Arrays.asList("any")); + Destination destinationSpy2 = spy(destination2); + Id destinationId2 = router + .registerDestination(destinationSpy2); + assertTrue(router.publish("any", source1)); + verify(destinationSpy, never()).receive(same(source1)); + verify(destinationSpy2).receive(same(source1)); + } + + @Test + public void testDestinationChooseFromTargetTypesReturnsNull() { + destinationSpy = registerDestination(true, "any"); + registerDocumentType("any"); + + when( + destinationSpy + .chooseFromTargetTypes((Collection) anyObject())) + .thenReturn(null); + + assertFalse(router.publish("any", source1)); + verify(destinationSpy, never()).receive(same(source1)); + } + + @Test + public void testDestinationChooseFromTargetTypesReturnsNullSecondDestinationStillOk() { + testDestinationChooseFromTargetTypesReturnsNull(); + + Destination destination2 = new MyDestination(true, Arrays.asList("any")); + Destination destinationSpy2 = spy(destination2); + Id destinationId2 = router + .registerDestination(destinationSpy2); + assertTrue(router.publish("any", source1)); + verify(destinationSpy, never()).receive(same(source1)); + verify(destinationSpy2).receive(same(source1)); + } + + @Test + public void testOneTransformationOneDestination() { + registerDocumentType("any"); + Transformation transformation = mock(Transformation.class); + when(transformation.getFromType()).thenReturn("any"); + when(transformation.getToType()).thenReturn("bla"); + when(transformation.transform(same(source1))).thenReturn(source2); + router.addTransformation(transformation); + + Destination destination = mock(Destination.class); + when( + destination.chooseFromTargetTypes((Collection) anyObject())) + .thenReturn(Arrays.asList("bla")); + + router.registerDestination(destination); + + when(destination.receive(any(DOMSource.class))).thenReturn(true); + assertTrue(router.publish("bla", source1)); + + verify(transformation).transform(source1); + verify(destination).receive(same(source2)); + + // now the same when the destination rejects the event. + when(destination.receive(any(DOMSource.class))).thenReturn(false); + assertFalse(router.publish("bla", source1)); + } + + private Transformation createTransformation(String aFrom, String aTo, + DOMSource aSource, DOMSource aTarget) { + Transformation transformation = mock(Transformation.class); + when(transformation.getFromType()).thenReturn(aFrom); + when(transformation.getToType()).thenReturn(aTo); + when(transformation.transform(same(aSource))).thenReturn(aTarget); + return transformation; + } + + @Test + public void testOneTransformationReturnsNull() { + registerDocumentType("any"); + Transformation transformation = createTransformation("any", "bla", + source1, null); + + router.addTransformation(transformation); + + Destination destination = mock(Destination.class); + when( + destination.chooseFromTargetTypes((Collection) anyObject())) + .thenReturn(Arrays.asList("bla")); + router.registerDestination(destination); + + assertFalse(router.publish("bla", source1)); + + verify(transformation).transform(source1); + verify(destination, never()).receive(any(DOMSource.class)); + + // add second transformation that behaves normally + Transformation transformation2 = createTransformation("any", "bla2", + source1, source2); + + router.addTransformation(transformation2); + when( + destination.chooseFromTargetTypes((Collection) anyObject())) + .thenReturn(Arrays.asList("bla", "bla2")); + + reset(transformation); + when(transformation.getFromType()).thenReturn("any"); + when(transformation.getToType()).thenReturn("bla"); + when(transformation.transform(same(source1))).thenReturn(null); + + when(destination.receive(any(DOMSource.class))).thenReturn(true); + assertTrue(router.publish("bla", source1)); + + verify(transformation).transform(source1); + verify(transformation2).transform(source1); + + verify(destination).receive(same(source2)); + + } + + @Test + public void testChooseMultipleDestinationsOneType() { + Destination dest1 = registerDestination(true, "any"); + Destination dest2 = registerDestination(true, "any"); + registerDocumentType("any"); + + assertTrue(router.publish("source", source1)); + + verify(dest1).receive(same(source1)); + verify(dest2).receive(same(source1)); + } + + @Test + public void testMultipleDeliveryToOneDestination() { + Destination dest = registerDestination(true, "any", "other"); + registerDocumentType("any", source1); + registerDocumentType("other", source2); + Transformation transformation = createTransformation("any", "other", + source1, source2); + router.addTransformation(transformation); + + assertTrue(router.publish("source", source1)); + + verify(dest).receive(same(source1)); + verify(dest).receive(same(source2)); + } + + @Test + public void testMultipleTransformations() { + Destination dest = registerDestination(true, "any", "other"); + registerDocumentType("any", source1); + registerDocumentType("other", source3); + + Transformation t1 = createTransformation("any", "intermediate", + source1, source2); + router.addTransformation(t1); + Transformation t2 = createTransformation("intermediate", "other", + source2, source3); + router.addTransformation(t2); + + assertTrue(router.publish("source", source1)); + + verify(dest).receive(same(source3)); + } +} diff --git a/publish/pom.xml b/publish/pom.xml new file mode 100644 index 0000000..9f96ec8 --- /dev/null +++ b/publish/pom.xml @@ -0,0 +1,34 @@ + + + + + org.wamblee.xmlrouter + xmlrouter-root + 0.1-SNAPSHOT + + + 4.0.0 + xmlrouter-publish + jar + /xmlrouter/publish + http://wamblee.org + + + + junit + junit + + + org.mockito + mockito-all + + + + + + xmlrouter-site + file:${distrib}/cache + + + + diff --git a/publish/src/main/java/org/wamblee/xmlrouter/publish/Gateway.java b/publish/src/main/java/org/wamblee/xmlrouter/publish/Gateway.java new file mode 100644 index 0000000..f181a27 --- /dev/null +++ b/publish/src/main/java/org/wamblee/xmlrouter/publish/Gateway.java @@ -0,0 +1,37 @@ +/* + * Copyright 2005-2011 the original author or authors. + * + * 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.wamblee.xmlrouter.publish; + +import javax.xml.transform.dom.DOMSource; + +/** + * XML router interface for publishing events. + * + * @author Erik Brakkee + */ +public interface Gateway { + + /** + * Publishes an event. + * + * @param aSource + * Source. + * @param aEvent + * Event. + * @return True iff th event was delivered to at least one destination. + */ + boolean publish(String aSource, DOMSource aEvent); +} diff --git a/subscribe/pom.xml b/subscribe/pom.xml new file mode 100644 index 0000000..a429e9b --- /dev/null +++ b/subscribe/pom.xml @@ -0,0 +1,39 @@ + + + + + org.wamblee.xmlrouter + xmlrouter-root + 0.1-SNAPSHOT + + + 4.0.0 + xmlrouter-subscribe + jar + /xmlrouter/subscribe + http://wamblee.org + + + + org.wamblee.xmlrouter + xmlrouter-common + 0.1-SNAPSHOT + + + junit + junit + + + org.mockito + mockito-all + + + + + + xmlrouter-site + file:${distrib}/cache + + + + diff --git a/subscribe/src/main/java/org/wamblee/xmlrouter/subscribe/Destination.java b/subscribe/src/main/java/org/wamblee/xmlrouter/subscribe/Destination.java new file mode 100644 index 0000000..a0c7b94 --- /dev/null +++ b/subscribe/src/main/java/org/wamblee/xmlrouter/subscribe/Destination.java @@ -0,0 +1,30 @@ +/* + * Copyright 2005-2011 the original author or authors. + * + * 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.wamblee.xmlrouter.subscribe; + +import java.util.Collection; + +import javax.xml.transform.dom.DOMSource; + +public interface Destination { + + String getName(); + + Collection chooseFromTargetTypes( + Collection aPossibleTargetTypes); + + boolean receive(DOMSource aEvent); +} diff --git a/subscribe/src/main/java/org/wamblee/xmlrouter/subscribe/DestinationRegistry.java b/subscribe/src/main/java/org/wamblee/xmlrouter/subscribe/DestinationRegistry.java new file mode 100644 index 0000000..87f9265 --- /dev/null +++ b/subscribe/src/main/java/org/wamblee/xmlrouter/subscribe/DestinationRegistry.java @@ -0,0 +1,39 @@ +/* + * Copyright 2005-2011 the original author or authors. + * + * 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.wamblee.xmlrouter.subscribe; + +import org.wamblee.xmlrouter.common.Id; + +public interface DestinationRegistry { + + /** + * @param aDestination + * Destination. + * @return Registration id. + * @throws RegistrationException + * In case registration fails. + */ + Id registerDestination(Destination aDestination); + + /** + * + * @param aRegistration + * Registration id. + * @throws RegistrationException + * In case the unregistration could not be done. + */ + void unregisterDestination(Id aRegistration); +} diff --git a/subscribe/src/main/java/org/wamblee/xmlrouter/subscribe/RegistrationException.java b/subscribe/src/main/java/org/wamblee/xmlrouter/subscribe/RegistrationException.java new file mode 100644 index 0000000..7b57e8b --- /dev/null +++ b/subscribe/src/main/java/org/wamblee/xmlrouter/subscribe/RegistrationException.java @@ -0,0 +1,28 @@ +/* + * Copyright 2005-2011 the original author or authors. + * + * 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.wamblee.xmlrouter.subscribe; + + +public class RegistrationException extends RuntimeException { + + public RegistrationException(String aMsg) { + super(aMsg); + } + + public RegistrationException(String aMsg, Throwable aCause) { + super(aMsg, aCause); + } +} -- 2.31.1