2 * Copyright 2005-2011 the original author or authors.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package org.wamblee.xmlrouter.impl;
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.HashSet;
21 import java.util.LinkedHashMap;
22 import java.util.List;
25 import java.util.concurrent.atomic.AtomicLong;
26 import java.util.logging.Level;
27 import java.util.logging.Logger;
29 import javax.xml.transform.dom.DOMSource;
31 import org.wamblee.general.Clock;
32 import org.wamblee.xml.XMLDocument;
33 import org.wamblee.xmlrouter.common.Id;
34 import org.wamblee.xmlrouter.config.Config;
35 import org.wamblee.xmlrouter.config.DocumentType;
36 import org.wamblee.xmlrouter.config.Filter;
37 import org.wamblee.xmlrouter.config.RouterConfig;
38 import org.wamblee.xmlrouter.config.Transformation;
39 import org.wamblee.xmlrouter.listener.EventInfo;
40 import org.wamblee.xmlrouter.listener.EventListener;
41 import org.wamblee.xmlrouter.publish.Gateway;
42 import org.wamblee.xmlrouter.subscribe.Destination;
43 import org.wamblee.xmlrouter.subscribe.DestinationRegistry;
48 * @author Erik Brakkee
51 public class XMLRouter implements RouterConfig, Gateway, DestinationRegistry {
53 private static final Logger LOGGER = Logger.getLogger(XMLRouter.class
56 private AtomicLong sequenceNumbers;
57 private EventListener listener;
59 private AtomicLong nextEventId;
61 private ExtendedRouterConfig routerConfig;
62 private Transformations transformations;
64 private Map<Id<Destination>, Destination> destinations;
66 public XMLRouter(Clock aClock, EventListener aListener) {
67 sequenceNumbers = new AtomicLong(1);
70 nextEventId = new AtomicLong(clock.currentTimeMillis());
71 routerConfig = new SingleRouterConfig(sequenceNumbers);
72 transformations = new Transformations();
73 destinations = new LinkedHashMap<Id<Destination>, Destination>();
77 public Config<DocumentType> documentTypeConfig() {
78 return routerConfig.documentTypeConfig();
82 public Config<Transformation> transformationConfig() {
83 return routerConfig.transformationConfig();
87 public Config<Filter> filterConfig() {
88 return routerConfig.filterConfig();
92 public void publish(String aSource, DOMSource aEvent) {
93 long time = clock.currentTimeMillis();
95 if (routerConfig.isDirty()) {
96 transformations.replaceTransformations(routerConfig
97 .transformationConfig().map());
98 routerConfig.resetDirty();
101 Id<DOMSource> id = new Id<DOMSource>(nextEventId.getAndIncrement());
102 List<String> types = determineDocumentTypes(aEvent);
103 EventInfo info = new EventInfo(time, aSource, id, types, aEvent);
105 boolean delivered = false;
108 List<String> filteredInputTypes = determineFilteredInputTypes(
110 if (filteredInputTypes.isEmpty()) {
111 if (LOGGER.isLoggable(Level.FINE)) {
112 String doc = new XMLDocument(aEvent).print(true);
116 "Event ''0}'' from source {1} removed because of filters.",
117 new Object[] { doc, aSource });
121 // get the reachable target types through transformations.
123 // It is possible that a given event belongs to multiple input
125 // This is however certainly not the main case.
127 for (String inputType : filteredInputTypes) {
128 boolean result = deliverEvent(info, inputType);
129 delivered = delivered || result;
133 destinationNotFound(aSource, aEvent);
134 listener.notDelivered(info);
139 private boolean deliverEvent(EventInfo aInfo, String aInputType) {
141 boolean delivered = false;
142 Set<String> possibleTargetTypes = new HashSet<String>();
143 possibleTargetTypes.addAll(transformations
144 .getPossibleTargetTypes(aInputType));
146 // ask each destination what target types, if any they want to have.
147 for (Map.Entry<Id<Destination>, Destination> entry : destinations
149 Id<Destination> destinationId = entry.getKey();
150 Destination destination = entry.getValue();
151 Collection<String> requested = destination
152 .chooseFromTargetTypes(possibleTargetTypes);
153 if (!requested.isEmpty()) {
154 // Deliver to the destination.
155 for (String targetType : requested) {
156 TransformationPath path = transformations.getPath(
157 aInputType, targetType);
158 List<Transformation> ts = path.getTransformations();
160 boolean allowed = true;
161 DOMSource transformed = aInfo.getEvent();
162 while (i < ts.size() && allowed && transformed != null) {
163 Transformation t = ts.get(i);
164 DOMSource orig = transformed;
165 transformed = t.transform(transformed);
166 if (transformed == null) {
167 transformationReturnedNull(aInfo.getSource(),
168 aInfo.getEvent(), aInputType, t, orig);
171 if (!isAllowedByFilters(t.getToType(), transformed)) {
176 if (allowed && transformed != null) {
177 // all transformations done and all filters still
179 boolean result = destination.receive(transformed);
180 listener.delivered(aInfo, ts, destinationId.getId(),
181 destination.getName(), result);
182 delivered = delivered || result;
191 private List<String> determineFilteredInputTypes(List<String> aTypes,
194 // apply filters to the input
195 List<String> filteredTypes = new ArrayList<String>();
196 for (String type : aTypes) {
197 boolean allowed = isAllowedByFilters(type, aEvent);
199 filteredTypes.add(type);
202 return filteredTypes;
205 private boolean isAllowedByFilters(String aType, DOMSource aEvent) {
206 boolean allowed = true;
207 for (Filter filter : routerConfig.filterConfig().map().values()) {
208 if (!filter.isAllowed(aType, aEvent)) {
215 private List<String> determineDocumentTypes(DOMSource aEvent) {
216 List<String> res = new ArrayList<String>();
217 for (DocumentType type : routerConfig.documentTypeConfig().map()
219 if (type.isInstance(aEvent)) {
220 res.add(type.getName());
226 private void logEvent(String aMessage, String aSource, DOMSource aEvent) {
227 LOGGER.log(Level.WARNING,
228 aMessage + ": " + eventToString(aSource, aEvent));
231 private String eventToString(String aSource, DOMSource aEvent) {
232 return "source '" + aSource + "': Event: '" +
233 new XMLDocument(aEvent).print(true) + "'";
236 private void transformationReturnedNull(String aSource, DOMSource aEvent,
237 String aInputType, Transformation aT, DOMSource aTransformed) {
238 LOGGER.log(Level.WARNING, "Transformation returned null for event " +
239 eventToString(aSource, aEvent) + " inputType '" + aInputType +
240 "', transformation '" + aT + "' document to transform " +
241 new XMLDocument(aTransformed).print(true));
244 private void destinationNotFound(String aSource, DOMSource aEvent) {
245 LOGGER.log(Level.WARNING, "No destination found for event: " +
246 eventToString(aSource, aEvent));
250 public Id<Destination> registerDestination(Destination aDestination) {
251 notNull("destination", aDestination);
252 long seqno = sequenceNumbers.getAndIncrement();
253 Id<Destination> id = new Id<Destination>(seqno);
254 destinations.put(id, new RobustDestination(id, aDestination));
259 public void unregisterDestination(Id<Destination> aId) {
260 destinations.remove(aId);
263 private void notNull(String aName, Object aValue) {
264 if (aValue == null) {
265 throw new IllegalArgumentException("Parameter '" + aName +
266 "' may not be null");