View Javadoc

1   /*
2    * Copyright (c) 2002 Peter Antman, Teknik i Media  <peter.antman@tim.se>
3    *
4    * $Id: AmsterdamXindicePublisher.java,v 1.1.1.1 2004/05/19 14:10:47 pra Exp $
5    *
6    * This library is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Lesser General Public
8    * License as published by the Free Software Foundation; either
9    * version 2 of the License, or (at your option) any later version
10   * 
11   * This library is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   * Lesser General Public License for more details.
15   * 
16   * You should have received a copy of the GNU Lesser General Public
17   * License along with this library; if not, write to the Free Software
18   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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       &lt;filter className="org.backsource.xindice.AmsterdamXindicePublisher"&gt;
51          &lt;filter-conf&gt;
52            &lt;url&gt;xmldb:xindice:///db/test&lt;/url&gt;
53            &lt;collection&gt;senasteNytt&lt;/collection&gt;
54            &lt;create-collections&gt;true&lt;/create-collections&gt;
55            &lt;key-xpath&gt;/NewsML/NewsEnvelope/TransmissionId&lt;/key-xpath&gt;
56          &lt;/filter-conf&gt;
57        &lt;/filter&gt;
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      &lt;publisher className="org.backsource.xindice.AmsterdamXindicePublisher"&gt;
62        &lt;url&gt;xmldb:xindice:///db/test&lt;/url&gt;
63        &lt;!--&lt;collection&gt;test&lt;/collection&gt;--&gt;
64        &lt;collection-xpath&gt;//Subject[@Scheme="EdrumCategory"]/@FormalName&lt;/collection-xpath&gt;
65        &lt;create-collections&gt;true&lt;/create-collections&gt;
66        &lt;key-xpath&gt;/NewsML/NewsEnvelope/TransmissionId&lt;/key-xpath&gt;
67      &lt;/publisher&gt;
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        //validateCollection(col,true);
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          // Get a document do work on
106          Document doc = getDocument(message, getNext() != null);
107       
108 
109          // if we have a collection we use that,
110          // if we have an xpath to a collection (or collections) name we
111          // use that
112          // otherwise we bail!
113          String[] collections = getCollections(doc,createCols);
114 
115          // Get key from doc if we have xpath
116          String key =  message.getUrl();//Default
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          } // end of if ()
132 
133          // GET ARTICLE ID
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          // Did we get any collection?
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                   // Add document
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                   //e.printStackTrace();
158                   throw new ServiceException("Could not add document to collection " + c + ":" +e,e);
159                }
160                finally {
161                   if ( ad != null) {
162                      // ALLWAYS leave it back.
163                      try {                 
164                         pool.leaveAdapter(ad);
165                      } catch (XindiceException e) {
166                         log.error("Could not leave back adapter for collection: " +c);
167                      } // end of try-catch
168                   } // end of if ()
169                
170                } // end of finally
171             
172             
173             } // end of for ()
174          
175          } else {
176             log.warn("Found no collection to work on");
177          } // end of else
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          } // end of else
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       } // end of try-catch
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          // May be both a filter and publisher
204          if ( "filter".equals(element.getNodeName())) {
205             element = ElementUtil.getUniqueChild(element, "filter-conf");
206          } // end of if ()
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          // testhack
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                //e.printStackTrace();
278                throw new XindiceException("Could create collection " + colName + ":" +e,e);
279             }finally {
280                if ( ad != null) {
281                   // ALLWAYS leave it back.
282                   try {                 
283                      pool.leaveAdapter(ad);
284                   } catch (XindiceException e) {
285                      log.error("Could not leave back adapter for collection: " +colName);
286                   } // end of try-catch
287                } // end of if ()
288             }
289             
290          }
291          else
292          {
293             throw new XindiceException("Collection " + colName + " does not exist and create is false");
294          } // end of else   
295       }
296       else
297       {
298          valid = true;
299       } // end of else
300       log.debug("Collection " + colName + "was valid="+valid);
301       return valid;
302    }
303    
304 }// AmsterdamXindicePublisher
305 
306 
307