introduction of EventInfo.
[xmlrouter] / impl / src / main / java / org / wamblee / xmlrouter / impl / XMLRouter.java
1 /*
2  * Copyright 2005-2011 the original author or authors.
3  * 
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
7  * 
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * 
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.
15  */
16 package org.wamblee.xmlrouter.impl;
17
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.Collections;
21 import java.util.HashSet;
22 import java.util.LinkedHashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26 import java.util.concurrent.atomic.AtomicInteger;
27 import java.util.logging.Level;
28 import java.util.logging.Logger;
29
30 import javax.xml.transform.dom.DOMSource;
31
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.Transformation;
38 import org.wamblee.xmlrouter.listener.EventListener;
39 import org.wamblee.xmlrouter.publish.Gateway;
40 import org.wamblee.xmlrouter.subscribe.Destination;
41 import org.wamblee.xmlrouter.subscribe.DestinationRegistry;
42
43 // TODO concurrency.
44
45 public class XMLRouter implements Config, Gateway, DestinationRegistry {
46
47     private static final Logger LOGGER = Logger.getLogger(XMLRouter.class
48         .getName());
49
50     private EventListener listener;
51     private AtomicInteger sequenceNumbers;
52     private Map<Integer, DocumentType> documentTypes;
53     private Transformations transformations;
54     private Map<Integer, Filter> filters;
55     private Map<Integer, Destination> destinations;
56
57     public XMLRouter(EventListener aListener) {
58         listener = aListener;
59         sequenceNumbers = new AtomicInteger(1);
60         documentTypes = new LinkedHashMap<Integer, DocumentType>();
61         transformations = new Transformations();
62         filters = new LinkedHashMap<Integer, Filter>();
63         destinations = new LinkedHashMap<Integer, Destination>();
64     }
65
66     @Override
67     public Id<DocumentType> addDocumentType(DocumentType aType) {
68         int seqno = sequenceNumbers.getAndIncrement();
69         documentTypes.put(seqno, aType);
70         return new Id<DocumentType>(seqno);
71     }
72
73     @Override
74     public void removeDocumentType(Id<DocumentType> aId) {
75         documentTypes.remove(aId);
76     }
77
78     @Override
79     public Collection<DocumentType> getDocumentTypes() {
80         return Collections.unmodifiableCollection(documentTypes.values());
81     }
82
83     @Override
84     public Id<Transformation> addTransformation(Transformation aTransformation) {
85         return transformations.addTransformation(aTransformation);
86     }
87
88     @Override
89     public void removeTransformation(Id<Transformation> aId) {
90         transformations.removeTransformation(aId);
91     }
92
93     @Override
94     public Collection<Transformation> getTransformations() {
95         return transformations.getTransformations();
96     }
97
98     @Override
99     public Id<Filter> addFilter(Filter aFilter) {
100         int seqno = sequenceNumbers.getAndIncrement();
101         filters.put(seqno, aFilter);
102         return new Id<Filter>(seqno);
103     }
104
105     @Override
106     public void removeFilter(Id<Filter> aId) {
107         filters.remove(aId);
108     }
109
110     @Override
111     public Collection<Filter> getFilters() {
112         return Collections.unmodifiableCollection(filters.values());
113     }
114
115     @Override
116     public boolean publish(String aSource, DOMSource aEvent) {
117
118         boolean delivered = false;
119         try {
120
121             List<String> filteredInputTypes = determineFilteredInputTypes(aEvent);
122             if (filteredInputTypes.isEmpty()) {
123                 if (LOGGER.isLoggable(Level.FINE)) {
124                     String doc = new XMLDocument(aEvent).print(true);
125                     LOGGER
126                         .log(
127                             Level.FINE,
128                             "Event ''0}'' from source {1} removed because of filters.",
129                             new Object[] { doc, aSource });
130                 }
131                 return delivered;
132             }
133
134             // get the reachable target types through transformations.
135
136             // It is possible that a given event belongs to multiple input
137             // types.
138             // This is however certainly not the main case.
139
140             for (String inputType : filteredInputTypes) {
141                 boolean result = deliverEvent(aSource, aEvent, inputType);
142                 delivered = delivered || result;
143             }
144         } finally {
145             if (!delivered) {
146                 destinationNotFound(aSource, aEvent);
147             }
148             return delivered;
149         }
150     }
151
152     private boolean deliverEvent(String aSource, DOMSource aEvent,
153         String aInputType) {
154
155         boolean delivered = false;
156         Set<String> possibleTargetTypes = new HashSet<String>();
157         possibleTargetTypes.addAll(transformations
158             .getPossibleTargetTypes(aInputType));
159
160         // ask each destination what target types, if any they want to have.
161         for (Destination destination : destinations.values()) {
162             Collection<String> requested = destination
163                 .chooseFromTargetTypes(possibleTargetTypes);
164             if (!requested.isEmpty()) {
165                 // Deliver to the destination.
166                 for (String targetType : requested) {
167                     TransformationPath path = transformations.getPath(
168                         aInputType, targetType);
169                     List<Transformation> ts = path.getTransformations();
170                     int i = 0;
171                     boolean allowed = true;
172                     DOMSource transformed = aEvent;
173                     while (i < ts.size() && allowed && transformed != null) {
174                         Transformation t = ts.get(i);
175                         DOMSource orig = transformed;
176                         transformed = t.transform(transformed);
177                         if (transformed == null) {
178                             transformationReturnedNull(aSource, aEvent,
179                                 aInputType, t, orig);
180                         }
181
182                         if (!isAllowedByFilters(t.getToType(), transformed)) {
183                             allowed = false;
184                         }
185                         i++;
186                     }
187                     if (allowed && transformed != null) {
188                         // all transformations done and all filters still
189                         // allow the event.
190                         boolean result = destination.receive(transformed);
191                         delivered = delivered || result;
192
193                     }
194                 }
195             }
196         }
197         return delivered;
198     }
199
200     private List<String> determineFilteredInputTypes(DOMSource aEvent) {
201         List<String> types = determineDocumentTypes(aEvent);
202         // apply filters to the input
203         List<String> filteredTypes = new ArrayList<String>();
204         for (String type : types) {
205             boolean allowed = isAllowedByFilters(type, aEvent);
206             if (allowed) {
207                 filteredTypes.add(type);
208             }
209         }
210         return filteredTypes;
211     }
212
213     private boolean isAllowedByFilters(String aType, DOMSource aEvent) {
214         boolean allowed = true;
215         for (Filter filter : filters.values()) {
216             if (!filter.isAllowed(aType, aEvent)) {
217                 allowed = false;
218             }
219         }
220         return allowed;
221     }
222
223     private List<String> determineDocumentTypes(DOMSource aEvent) {
224         List<String> res = new ArrayList<String>();
225         for (DocumentType type : documentTypes.values()) {
226             if (type.isInstance(aEvent)) {
227                 res.add(type.getName());
228             }
229         }
230         return res;
231     }
232
233     private void logEvent(String aMessage, String aSource, DOMSource aEvent,
234         Exception aException) {
235         LOGGER.log(Level.WARNING, aMessage + ": source '" + aSource +
236             "': Event: '" + new XMLDocument(aEvent).print(true) + "'",
237             aException);
238     }
239
240     private void logEvent(String aMessage, String aSource, DOMSource aEvent) {
241         LOGGER.log(Level.WARNING,
242             aMessage + ": " + eventToString(aSource, aEvent));
243     }
244
245     private String eventToString(String aSource, DOMSource aEvent) {
246         return "source '" + aSource + "': Event: '" +
247             new XMLDocument(aEvent).print(true) + "'";
248     }
249
250     private void transformationReturnedNull(String aSource, DOMSource aEvent,
251         String aInputType, Transformation aT, DOMSource aTransformed) {
252         LOGGER.log(Level.WARNING, "Transformation returned null for event " +
253             eventToString(aSource, aEvent) + " inputType '" + aInputType +
254             "', transformation '" + aT + "' document to transform " +
255             new XMLDocument(aTransformed).print(true));
256     }
257
258     private void destinationNotFound(String aSource, DOMSource aEvent) {
259         LOGGER.log(Level.WARNING, "No destination found for event: " +
260             eventToString(aSource, aEvent));
261     }
262
263     @Override
264     public Id<Destination> registerDestination(Destination aDestination) {
265         notNull("destination", aDestination);
266         int seqno = sequenceNumbers.getAndIncrement();
267         Id<Destination> id = new Id<Destination>(seqno);
268         destinations.put(seqno, new RobustDestination(id, aDestination));
269         return id;
270     }
271
272     @Override
273     public void unregisterDestination(Id<Destination> aId) {
274         destinations.remove(aId.getId());
275     }
276
277     private void notNull(String aName, Object aValue) {
278         if (aValue == null) {
279             throw new IllegalArgumentException("Parameter '" + aName +
280                 "' may not be null");
281         }
282     }
283 }