--- /dev/null
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee.xmlrouter</groupId>
+ <artifactId>xmlrouter-root</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>xmlrouter-common</artifactId>
+ <packaging>jar</packaging>
+ <name>/xmlrouter/common</name>
+ <url>http://wamblee.org</url>
+
+
+ <distributionManagement>
+ <site>
+ <id>xmlrouter-site</id>
+ <url>file:${distrib}/cache</url>
+ </site>
+ </distributionManagement>
+
+</project>
--- /dev/null
+package org.wamblee.xmlrouter.common;
+
+public class Id<T> {
+
+ 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<T>) aObj).id;
+ }
+
+ @Override
+ public String toString() {
+ return id + "";
+ }
+}
--- /dev/null
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee.xmlrouter</groupId>
+ <artifactId>xmlrouter-root</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>xmlrouter-config</artifactId>
+ <packaging>jar</packaging>
+ <name>/xmlrouter/config</name>
+ <url>http://wamblee.org</url>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee.xmlrouter</groupId>
+ <artifactId>xmlrouter-common</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee</groupId>
+ <artifactId>wamblee-support-general</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ </dependency>
+ </dependencies>
+
+ <distributionManagement>
+ <site>
+ <id>xmlrouter-site</id>
+ <url>file:${distrib}/cache</url>
+ </site>
+ </distributionManagement>
+
+</project>
--- /dev/null
+/*
+ * 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<DocumentType> addDocumentType(DocumentType aType);
+
+ void removeDocumentType(Id<DocumentType> aId);
+
+ Collection<DocumentType> getDocumentTypes();
+
+ Id<Transformation> addTransformation(Transformation aTransformation);
+
+ void removeTransformation(Id<Transformation> aId);
+
+ Collection<Transformation> getTransformations();
+
+ Id<Filter> addFilter(Filter aFilter);
+
+ void removeFilter(Id<Filter> aId);
+
+ Collection<Filter> getFilters();
+
+}
--- /dev/null
+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);
+}
--- /dev/null
+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);
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+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);
+
+}
--- /dev/null
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee.xmlrouter</groupId>
+ <artifactId>xmlrouter-root</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>xmlrouter-impl</artifactId>
+ <packaging>jar</packaging>
+ <name>/xmlrouter/impl</name>
+ <url>http://wamblee.org</url>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee.xmlrouter</groupId>
+ <artifactId>xmlrouter-config</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee.xmlrouter</groupId>
+ <artifactId>xmlrouter-publish</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wamblee.xmlrouter</groupId>
+ <artifactId>xmlrouter-subscribe</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ </dependency>
+ </dependencies>
+
+ <distributionManagement>
+ <site>
+ <id>xmlrouter-site</id>
+ <url>file:${distrib}/cache</url>
+ </site>
+ </distributionManagement>
+
+</project>
--- /dev/null
+package org.wamblee.xmlrouter.impl;
+
+public enum Constants {
+ UNKNOWN_DOCUMENT_TYPE, UNKNOWN_DESTINATION_NAME
+}
--- /dev/null
+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<Destination> id;
+ private Destination destination;
+
+ public RobustDestination(Id<Destination> 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<String> chooseFromTargetTypes(
+ Collection<String> aPossibleTargetTypes) {
+ try {
+ Collection<String> res = destination
+ .chooseFromTargetTypes(aPossibleTargetTypes);
+ if (res == null) {
+ logChooseFromTargetTypesReturnedNull(aPossibleTargetTypes);
+ return Collections.EMPTY_LIST;
+ }
+ Collection<String> finalRes = new ArrayList<String>();
+ 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<String> 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<String> aPossibleTargetTypes) {
+ LOGGER.log(Level.WARNING,
+ "chooseFromTargetTypes() returned null for destination " + id);
+ }
+
+ private void logChooseFromTargetTypesThrewException(
+ Collection<String> 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);
+ }
+
+}
--- /dev/null
+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<Filter> id;
+ private Filter filter;
+
+ public RobustFilter(Id<Filter> 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;
+ }
+ }
+}
--- /dev/null
+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<Transformation> id;
+ private Transformation transformation;
+
+ public RobustTransformation(Id<Transformation> 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));
+ }
+
+}
--- /dev/null
+package org.wamblee.xmlrouter.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.wamblee.xmlrouter.config.Transformation;
+
+public class TransformationPath {
+
+ private List<Transformation> transformations;
+
+ public TransformationPath() {
+ transformations = new ArrayList<Transformation>();
+ }
+
+ public TransformationPath(Transformation aTransformation) {
+ this();
+ transformations.add(aTransformation);
+ }
+
+ public TransformationPath(List<Transformation> 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<Transformation> t = new ArrayList<Transformation>(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<Transformation> 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();
+ }
+}
--- /dev/null
+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<Integer, Transformation> transformations;
+ private List<String> vertices;
+ private TransformationPath[][] matrix;
+
+ private Map<String, List<TransformationPath>> sequences;
+
+ public Transformations() {
+ sequenceNumber = new AtomicInteger(1);
+ transformations = new LinkedHashMap<Integer, Transformation>();
+ vertices = new ArrayList<String>();
+ matrix = new TransformationPath[0][0];
+ }
+
+ public Id<Transformation> addTransformation(Transformation aTransformation) {
+ int seqno = sequenceNumber.getAndIncrement();
+ Id<Transformation> id = new Id<Transformation>(seqno);
+ transformations.put(seqno,
+ new RobustTransformation(id, aTransformation));
+ computeTransformationSequences();
+ return id;
+ }
+
+ public Collection<String> getPossibleTargetTypes(String aType) {
+ int index = vertices.indexOf(aType);
+ Set<String> res = new HashSet<String>();
+ 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<String>();
+
+ // Obtain possible starting points.
+ Set<String> v = new HashSet<String>();
+ 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<Transformation> aId) {
+ transformations.remove(aId.getId());
+ computeTransformationSequences();
+ }
+
+ public Collection<Transformation> 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
--- /dev/null
+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<Integer, DocumentType> documentTypes;
+ private Transformations transformations;
+ private Map<Integer, Filter> filters;
+ private Map<Integer, Destination> destinations;
+
+ public XMLRouter() {
+ sequenceNumbers = new AtomicInteger(1);
+ documentTypes = new LinkedHashMap<Integer, DocumentType>();
+ transformations = new Transformations();
+ filters = new LinkedHashMap<Integer, Filter>();
+ destinations = new LinkedHashMap<Integer, Destination>();
+ }
+
+ @Override
+ public Id<DocumentType> addDocumentType(DocumentType aType) {
+ int seqno = sequenceNumbers.getAndIncrement();
+ documentTypes.put(seqno, aType);
+ return new Id<DocumentType>(seqno);
+ }
+
+ @Override
+ public void removeDocumentType(Id<DocumentType> aId) {
+ documentTypes.remove(aId);
+ }
+
+ @Override
+ public Collection<DocumentType> getDocumentTypes() {
+ return Collections.unmodifiableCollection(documentTypes.values());
+ }
+
+ @Override
+ public Id<Transformation> addTransformation(Transformation aTransformation) {
+ return transformations.addTransformation(aTransformation);
+ }
+
+ @Override
+ public void removeTransformation(Id<Transformation> aId) {
+ transformations.removeTransformation(aId);
+ }
+
+ @Override
+ public Collection<Transformation> getTransformations() {
+ return transformations.getTransformations();
+ }
+
+ @Override
+ public Id<Filter> addFilter(Filter aFilter) {
+ int seqno = sequenceNumbers.getAndIncrement();
+ filters.put(seqno, aFilter);
+ return new Id<Filter>(seqno);
+ }
+
+ @Override
+ public void removeFilter(Id<Filter> aId) {
+ filters.remove(aId);
+ }
+
+ @Override
+ public Collection<Filter> getFilters() {
+ return Collections.unmodifiableCollection(filters.values());
+ }
+
+ @Override
+ public boolean publish(String aSource, DOMSource aEvent) {
+
+ boolean delivered = false;
+ try {
+
+ List<String> 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<String> possibleTargetTypes = new HashSet<String>();
+ possibleTargetTypes.addAll(transformations
+ .getPossibleTargetTypes(aInputType));
+
+ // ask each destination what target types, if any they want to have.
+ for (Destination destination : destinations.values()) {
+ Collection<String> requested = destination
+ .chooseFromTargetTypes(possibleTargetTypes);
+ if (!requested.isEmpty()) {
+ // Deliver to the destination.
+ for (String targetType : requested) {
+ TransformationPath path = transformations.getPath(
+ aInputType, targetType);
+ List<Transformation> 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<String> determineFilteredInputTypes(DOMSource aEvent) {
+ List<String> types = determineDocumentTypes(aEvent);
+ // apply filters to the input
+ List<String> filteredTypes = new ArrayList<String>();
+ 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<String> determineDocumentTypes(DOMSource aEvent) {
+ List<String> res = new ArrayList<String>();
+ 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<Destination> registerDestination(Destination aDestination) {
+ notNull("destination", aDestination);
+ int seqno = sequenceNumbers.getAndIncrement();
+ Id<Destination> id = new Id<Destination>(seqno);
+ destinations.put(seqno, new RobustDestination(id, aDestination));
+ return id;
+ }
+
+ @Override
+ public void unregisterDestination(Id<Destination> aId) {
+ destinations.remove(aId.getId());
+ }
+
+ private void notNull(String aName, Object aValue) {
+ if (aValue == null) {
+ throw new IllegalArgumentException("Parameter '" + aName +
+ "' may not be null");
+ }
+ }
+}
--- /dev/null
+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<Destination>(100), destination);
+ source = mock(DOMSource.class);
+ }
+
+ @Test
+ public void testNormalFlow() {
+ when(destination.getName()).thenReturn("name");
+ when(
+ destination.chooseFromTargetTypes((Collection<String>) 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<String>) 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<String>) 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));
+ }
+}
--- /dev/null
+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<Filter>(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));
+
+ }
+}
--- /dev/null
+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<Transformation>(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));
+ }
+}
--- /dev/null
+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<Transformation> 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<String> possibleTargets = transformations
+ .getPossibleTargetTypes("A");
+ assertEquals(2, possibleTargets.size());
+ assertTrue(possibleTargets.contains("A"));
+ assertTrue(possibleTargets.contains("B"));
+ }
+
+ @Test
+ public void testMultipleTransformations() {
+ Id<Transformation> seqno1 = transformations
+ .addTransformation(new MyTransformation("A", "B"));
+ Id<Transformation> seqno2 = transformations
+ .addTransformation(new MyTransformation("B", "C"));
+ Id<Transformation> 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<String> 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"));
+ }
+}
--- /dev/null
+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<String> types;
+
+ public MyDestination(boolean aReceiveResult, Collection<String> aTypes) {
+ receiveResult = aReceiveResult;
+ types = aTypes;
+ }
+
+ @Override
+ public Collection<String> chooseFromTargetTypes(
+ Collection<String> 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<Destination> 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<DocumentType> 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<DocumentType> 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<Destination> 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<String>) 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<Destination> 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<String>) 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<Destination> 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<String>) 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<String>) 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<String>) 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));
+ }
+}
--- /dev/null
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee.xmlrouter</groupId>
+ <artifactId>xmlrouter-root</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>xmlrouter-publish</artifactId>
+ <packaging>jar</packaging>
+ <name>/xmlrouter/publish</name>
+ <url>http://wamblee.org</url>
+
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ </dependency>
+ </dependencies>
+
+ <distributionManagement>
+ <site>
+ <id>xmlrouter-site</id>
+ <url>file:${distrib}/cache</url>
+ </site>
+ </distributionManagement>
+
+</project>
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <parent>
+ <groupId>org.wamblee.xmlrouter</groupId>
+ <artifactId>xmlrouter-root</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>xmlrouter-subscribe</artifactId>
+ <packaging>jar</packaging>
+ <name>/xmlrouter/subscribe</name>
+ <url>http://wamblee.org</url>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wamblee.xmlrouter</groupId>
+ <artifactId>xmlrouter-common</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ </dependency>
+ </dependencies>
+
+ <distributionManagement>
+ <site>
+ <id>xmlrouter-site</id>
+ <url>file:${distrib}/cache</url>
+ </site>
+ </distributionManagement>
+
+</project>
--- /dev/null
+/*
+ * 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<String> chooseFromTargetTypes(
+ Collection<String> aPossibleTargetTypes);
+
+ boolean receive(DOMSource aEvent);
+}
--- /dev/null
+/*
+ * 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<Destination> registerDestination(Destination aDestination);
+
+ /**
+ *
+ * @param aRegistration
+ * Registration id.
+ * @throws RegistrationException
+ * In case the unregistration could not be done.
+ */
+ void unregisterDestination(Id<Destination> aRegistration);
+}
--- /dev/null
+/*
+ * 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);
+ }
+}