1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.backsource.xindice;
21
22 import java.util.HashSet;
23 import java.io.ByteArrayInputStream;
24 import java.io.ByteArrayOutputStream;
25
26 import org.apache.log4j.Logger;
27
28 import org.w3c.dom.Document;
29 import org.w3c.dom.Element;
30 import org.w3c.dom.Node;
31 import org.w3c.dom.Attr;
32
33 import org.backsource.amsterdam.service.publisher.Publisher;
34 import org.backsource.amsterdam.service.filter.ServiceFilter;
35 import org.backsource.amsterdam.service.filter.ServiceFilterSupport;
36 import org.backsource.amsterdam.service.ServiceMessage;
37 import org.backsource.amsterdam.service.ServiceException;
38 import org.backsource.amsterdam.deployment.DeploymentException;
39 import org.backsource.amsterdam.metadata.XmlConfigurable;
40
41 import org.backsource.utils.xml.ElementUtil;
42 import org.backsource.utils.xml.XmlException;
43 import org.backsource.utils.xml.XPathUtil;
44 /***
45 * Amsterdam publisher for Xindice.
46 *
47 *<p>This class publishes the incomming document to the configured collections. It overrides the {@link #validateCollection} method to create any collection that it can not find if the <b>create-collections</b> flag is true. It is also possible to set a <b>key-xpath</b> expression so that it can lookup the key to save as from the incomming document. There is currently no way to just use a subset of the collections fethched from the xpath; to make collections name or skip certain collections, sublcass this class and override the {@link #getCollectionName} and/or {@link #validateCollection}</p>
48 <p>Here is a configuration that goes agains a single collections.</p>
49 <pre>
50 <filter className="org.backsource.xindice.AmsterdamXindicePublisher">
51 <filter-conf>
52 <url>xmldb:xindice:///db/test</url>
53 <collection>senasteNytt</collection>
54 <create-collections>true</create-collections>
55 <key-xpath>/NewsML/NewsEnvelope/TransmissionId</key-xpath>
56 </filter-conf>
57 </filter>
58 </pre>
59 <p>And here is a publisher config that dynalically selects the collections to save the document to (and creates them if they do not exist).</p>
60 <pre>
61 <publisher className="org.backsource.xindice.AmsterdamXindicePublisher">
62 <url>xmldb:xindice:///db/test</url>
63 <!--<collection>test</collection>-->
64 <collection-xpath>//Subject[@Scheme="EdrumCategory"]/@FormalName</collection-xpath>
65 <create-collections>true</create-collections>
66 <key-xpath>/NewsML/NewsEnvelope/TransmissionId</key-xpath>
67 </publisher>
68 </pre>
69 <p>Possible bugs</p>
70 <pre>
71 1. Have seen situations when loading of thow parallell messages with same category and where the category did not exist prior: seems as if the second one does not allways see that the collection was created by the first thread.
72
73 </pre>
74
75 *
76 *
77 * @author <a href="mailto:pra@tim.se">Peter Antman</a>
78 * @version $Revision: 1.1.1.1 $
79 */
80
81 public class AmsterdamXindicePublisher extends AmsterdamXindiceBase
82 implements Publisher,XmlConfigurable{
83 private static Logger log = Logger.getLogger( AmsterdamXindicePublisher.class);
84 protected boolean createCols = false;
85 protected String keyXpath = null;
86
87 public AmsterdamXindicePublisher (){
88
89 }
90
91 public void start() throws Exception
92 {
93
94 }
95
96
97
98 /***
99 * Publish the xml message to the collections this service was configured to i use.
100 * if getNext() returns null, we are a publisher, othervise we are a filter.
101 */
102 public void handleMessage(ServiceMessage message) throws ServiceException {
103 try {
104
105
106 Document doc = getDocument(message, getNext() != null);
107
108
109
110
111
112
113 String[] collections = getCollections(doc,createCols);
114
115
116 String key = message.getUrl();
117 if ( keyXpath != null) {
118 Node n = XPathUtil.selectSingleNode(doc,keyXpath);
119 if ( n != null) {
120 if ( n instanceof Element) {
121 key = ElementUtil.getContent((Element)n);
122 } else if ( n instanceof Attr ) {
123 key = n.getNodeValue();
124 } else {
125 throw new ServiceException("Node was not element nor attribute: " + n);
126 }
127 } else {
128 log.debug("Key for keyXpath was not found: "+keyXpath);
129 }
130
131 }
132
133
134 if(key != null)
135 {
136 log.debug("Key: " + key);
137 key = key.substring(0,key.indexOf(":"));
138 log.debug("Key: " + key);
139 }
140
141
142
143 if ( collections != null && collections.length > 0) {
144 for ( int i = 0; i < collections.length;i++) {
145
146 String c = collections[i];
147 XindiceAdapter ad = null;
148 try {
149
150 ad = pool.getAdapter( c );
151 log.debug("Adding document");
152 ad.addDocument(doc,
153 key,
154 true);
155 } catch (XindiceException e) {
156 log.error("Could not add document",e);
157
158 throw new ServiceException("Could not add document to collection " + c + ":" +e,e);
159 }
160 finally {
161 if ( ad != null) {
162
163 try {
164 pool.leaveAdapter(ad);
165 } catch (XindiceException e) {
166 log.error("Could not leave back adapter for collection: " +c);
167 }
168 }
169
170 }
171
172
173 }
174
175 } else {
176 log.warn("Found no collection to work on");
177 }
178
179
180 ServiceFilter nextFilter = getNext();
181 if ( nextFilter !=null) {
182 nextFilter.handleMessage(message);
183 } else {
184 log.debug("No next filter to invoke, assuming I am a publisher");
185 }
186
187
188
189
190 } catch (ServiceException e) {
191 throw e;
192 } catch (Exception e) {
193 throw new ServiceException("Could not handle message: " + e,e);
194 }
195 }
196
197 /***
198 * Takes argument as AmsterdamXindiceBase. Also takes the optional <b>create-collections</b> and <b>key-xpath</b>.
199 */
200 public void importXml(Element element) throws DeploymentException {
201 try {
202 super.importXml(element);
203
204 if ( "filter".equals(element.getNodeName())) {
205 element = ElementUtil.getUniqueChild(element, "filter-conf");
206 }
207
208 log.debug("Configuring from element "+element.getNodeName());
209 Element createColsEl = ElementUtil.getOptionalChild(element, "create-collections");
210 Element keyEl = ElementUtil.getOptionalChild(element, "key-xpath");
211
212 createCols = Boolean.valueOf( ElementUtil.getContent(createColsEl,"false") ).booleanValue();
213 keyXpath = ElementUtil.getContent(keyEl,null);
214
215
216 } catch (XmlException e) {
217 throw new DeploymentException("Error in configuring publisher: " +e,e);
218 }
219
220
221 log.info("Publisher set up: " + toString());
222 }
223
224 public String toString() {
225 return super.toString()+"/createCols="+createCols+"/keyXpath="+keyXpath;
226 }
227
228 /***
229 * Get the collection(s) this message should be saved in.
230 *
231 * <p>if col is set, that will be used, if xpath i set, the names of the
232 * collections will fetched from the document.if create is set to true, we will try to create the collection if it does not exist, otherwise an XindiceExceptiob will be thrown if the collections does not exist.
233 *
234 * @param doc document to save.
235 * @exception XindiceException if collection is not correct
236 * @exception XmlException if getting of collection with xpath failes.
237 */
238 protected String[] getCollections(Document doc, boolean create) throws XindiceException, XmlException{
239 return super.getCollections(doc,create);
240 }
241
242 /***
243 * Template method to use in sublclasses that wants to map incomming collection name to another, this version just returns the name.
244 * @return a collections name, wich may be a mapped name, or null if the collection is not a valid/allowed collection.
245 */
246 protected String getCollectionName(String colName) throws XindiceException{
247 return colName;
248 }
249
250 /***
251 * Validate that the collection exists, if create is true try create one.
252 *
253 * @param colName the collection to check.
254 * @param create create collection if it does not exists if set to true.
255 * @exception XindiceException if collection did not exist and create was false or if creation did not work.
256 */
257 protected boolean validateCollection(String colName,boolean create) throws XindiceException {
258 log.debug("validateCollection(" + colName + ", " + create + ")");
259 boolean valid = false;
260
261 if ( !existingCollections.contains(colName) )
262 {
263 updateExistingCollections();
264 }
265
266 if( !existingCollections.contains(colName) )
267 {
268 if ( create) {
269 XindiceAdapter ad = null;
270 try {
271 ad = pool.getAdapter( "/" );
272 ad.createCollection(colName);
273 existingCollections.add(colName);
274 valid = true;
275 } catch (XindiceException e) {
276 log.error("Could not create collection "+colName,e);
277
278 throw new XindiceException("Could create collection " + colName + ":" +e,e);
279 }finally {
280 if ( ad != null) {
281
282 try {
283 pool.leaveAdapter(ad);
284 } catch (XindiceException e) {
285 log.error("Could not leave back adapter for collection: " +colName);
286 }
287 }
288 }
289
290 }
291 else
292 {
293 throw new XindiceException("Collection " + colName + " does not exist and create is false");
294 }
295 }
296 else
297 {
298 valid = true;
299 }
300 log.debug("Collection " + colName + "was valid="+valid);
301 return valid;
302 }
303
304 }
305
306
307