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