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.io.InputStream;
23 import java.io.ByteArrayInputStream;
24 import java.io.ByteArrayOutputStream;
25 import java.io.IOException;
26 import java.util.HashSet;
27 import java.util.ArrayList;
28 import java.util.List;
29 import java.util.Iterator;
30
31 import org.apache.log4j.Logger;
32
33 import org.w3c.dom.Document;
34 import org.w3c.dom.Element;
35 import org.w3c.dom.Node;
36
37 import org.backsource.amsterdam.service.ServiceMessage;
38 import org.backsource.amsterdam.service.filter.ServiceFilterSupport;
39 import org.backsource.amsterdam.deployment.DeploymentException;
40 import org.backsource.amsterdam.service.ServiceException;
41 import org.backsource.amsterdam.metadata.XmlConfigurable;
42
43 import org.backsource.utils.xml.ElementUtil;
44 import org.backsource.utils.xml.DocumentUtil;
45 import org.backsource.utils.xml.XPathUtil;
46 import org.backsource.utils.xml.XmlException;
47 import org.backsource.utils.io.IOHelper;
48 /***
49 * A base class for Xindice Amsterdam components.
50 *
51 * <p>This base class may be used to create filters and publishers that work
52 * agains the Xindice XML DB.It should be configured with a base xindice uri, pointing to a base collection and either a specifyed collection to work agains or
53 an xpath to be used to get the collection names from the message.</p>
54 <p>Here is a possible configuration for filter with a preconfigured collection:<p>
55
56 <pre> <filter-conf>
57 <url>xmldb:xindice:///db/test</url>
58 <collection>senasteNytt</collection>
59 </filter-conf>
60 </pre>
61 <p>And here is for publisher with an xpath selecting collections: </p>
62 <pre>
63 <publisher className="org.backsource.xindice.AmsterdamXindicePublisher">
64 <url>xmldb:xindice:///db/test</url>
65 <collection-xpath>//Subject[@Scheme="EdrumCategory"]/@FormalName</collection-xpath>
66 </publisher>
67 </pre>
68
69 *
70 *
71 *
72 * @author <a href="mailto:pra@tim.se">Peter Antman</a>
73 * @version $Revision: 1.1.1.1 $
74 */
75
76 public abstract class AmsterdamXindiceBase extends ServiceFilterSupport
77 implements XmlConfigurable{
78 private static Logger log = Logger.getLogger(AmsterdamXindiceBase.class);
79 protected XindicePool pool;
80 protected HashSet existingCollections = new HashSet();
81 protected String url;
82 protected String col;
83 protected String xpath;
84
85 public AmsterdamXindiceBase (){
86
87 }
88
89 /***
90 * Start the component, get a list of all existing child collections.
91 */
92 public void start() throws Exception {
93 log.info("Starting publisher");
94
95 try
96 {
97 updateExistingCollections();
98 }
99 catch(Exception e)
100 {
101 throw new ServiceException("Could not list collections :" +e,e);
102 }
103 }
104
105 /***
106 * Moved here to make it possible to update the existing collections.
107 */
108 protected void updateExistingCollections() throws XindiceException {
109 String[] cols = null;
110
111 XindiceAdapter ad = null;
112 try {
113 ad = pool.getAdapter( "/" );
114 cols = ad.listCollection();
115 } catch (XindiceException e) {
116 log.error("Could not list collections",e);
117
118 throw e;
119
120 }finally {
121 if ( ad != null) {
122
123 try {
124 pool.leaveAdapter(ad);
125 } catch (XindiceException e) {
126 log.error("Could not leave back adapter for collection: NULL");
127 }
128 }
129
130
131 }
132
133 if ( cols != null) {
134 for (int i = 0;i<cols.length;i++) {
135 log.debug("Adding to existing collections: " + cols[i]);
136 existingCollections.add(cols[i]);
137 }
138
139 }
140 }
141
142 /***
143 * Stop component.
144 */
145 public void stop() throws Exception {
146 log.info("Stopping publisher");
147 if ( pool != null) {
148 pool.close();
149 }
150
151 }
152
153 public String toString() {
154 return "url="+url+"/col="+col+"/xpath="+xpath;
155 }
156
157 /***
158 * Return a DOM document from message, if isFilter is true, guarantee that message still contains a message.
159 */
160 protected Document getDocument(ServiceMessage message, boolean isFilter) throws ServiceException {
161 InputStream ms = null;
162
163 Document doc = null;
164 try {
165
166
167 if ( isFilter) {
168 if (message.hasStream()) {
169 byte[] input = null;
170 ByteArrayOutputStream dbout = new ByteArrayOutputStream();
171 try {
172 IOHelper.connect(message.getStream(),dbout);
173 } catch (IOException e) {
174 throw new ServiceException("Could not buffer message"+e,e);
175 }
176
177 input = dbout.toByteArray();
178 ByteArrayInputStream ris = new ByteArrayInputStream( input );
179 message.setInputStream(ris);
180 ms = new ByteArrayInputStream( input );
181 }
182 }
183
184
185 try {
186 if (message.hasStream()) {
187 doc = DocumentUtil.getDocument(message.getStream());
188 } else {
189 doc = DocumentUtil.getDocument(message.getMessage());
190 }
191 } catch (XmlException e) {
192 throw new ServiceException("Could not construct document: " +e,e);
193 }
194
195 if ( doc == null) {
196
197 throw new ServiceException("Could not construct document from message");
198 }
199
200 } finally {
201
202 if ( ms !=null) {
203 message.setInputStream(ms);
204 }
205
206 }
207 return doc;
208 }
209
210 /***
211 * Get the collection(s) connected to this configuration.
212 *
213 * <p>if col is set, that will be used, if xpath i set, the names of the
214 * collections will fetched from the document. {@link #getCollectionName} will be used
215 * to get any mapped name for the collection and {@link #validateCollection} will be used to validate the colection. Both these are typically overridden in subclasses to provide needed behaviuor.</p>
216 *
217 * @param doc document to save.
218 * @exception XindiceException if collection is not correct
219 * @exception XmlException if getting of collection with xpath failes.
220 */
221 protected String[] getCollections(Document doc, boolean create) throws XindiceException, XmlException{
222 String[] collections = null;
223 if ( col != null) {
224 if (validateCollection(col,create) ) {
225 collections = new String[] {col};
226 } else {
227 return new String[]{};
228 }
229
230 } else if (xpath != null) {
231 List list = XPathUtil.selectNodes(doc,xpath);
232
233 ArrayList valid = new ArrayList();
234 int i = 0;
235 for (Iterator it = list.iterator(); it.hasNext();i++){
236 Node n = (Node)it.next();
237 String c = getCollectionName( n.getNodeValue() );
238 if ( c != null && validateCollection(c ,create) ) {
239 valid.add(c);
240 }else {
241 log.warn(c +" not a valid collection");
242 }
243
244 }
245 collections = (String[])valid.toArray( new String[ valid.size() ]);
246 }
247 return collections;
248 }
249
250 /***
251 * Template method to use in sublclasses that wants to map incomming collection name to another, this version just returns the name.
252 * @return a collections name, wich may be a mapped name, or null if the collection is not a valid/allowed collection.
253 */
254 protected String getCollectionName(String colName) throws XindiceException{
255 return colName;
256 }
257
258 /***
259 * Validate that the collection exists, if create is true try create one.
260 *
261 * <p>The dafult impl just checks if the collections exist,and returns
262 * true if is does and false otherwise.</p>
263 *
264 * @param colName the collection to check.
265 * @param create create collection if it does not exists if set to true.
266 * @exception XindiceException if collection did not exist and create was false or if creation did not work.
267 */
268 protected boolean validateCollection(String colName,boolean create) throws XindiceException {
269 if ( existingCollections.contains(colName) ) {
270 return true;
271 } else {
272 return false;
273 }
274
275
276 }
277
278
279 public void importXml(Element element) throws DeploymentException {
280 try {
281 if ( "filter".equals(element.getNodeName())) {
282 element = ElementUtil.getUniqueChild(element, "filter-conf");
283 }
284
285 log.debug("Configuring from element "+element.getNodeName());
286
287 Element urlEl = ElementUtil.getUniqueChild(element, "url");
288 Element colEl = ElementUtil.getOptionalChild(element, "collection");
289 Element colXpath = ElementUtil.getOptionalChild(element, "collection-xpath");
290
291 url = ElementUtil.getContent(urlEl);
292 col = ElementUtil.getContent(colEl,null);
293 xpath = ElementUtil.getContent(colXpath,null);
294
295 if ( xpath == null && col == null) {
296 throw new DeploymentException("Must be configured with either a collection or an collection-xpath");
297 }
298
299 } catch (XmlException e) {
300 throw new DeploymentException("Error in configuring publisher: " +e,e);
301 }
302 pool = new XindicePool(url);
303
304 log.info("Base set up: " + toString());
305 }
306 }