View Javadoc

1   /*
2    * Copyright (c) 2002 Peter Antman, Teknik i Media  <peter.antman@tim.se>
3    *
4    * $Id: AmsterdamXindiceBase.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.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>   &lt;filter-conf&gt;
57            &lt;url&gt;xmldb:xindice:///db/test&lt;/url&gt;
58            &lt;collection&gt;senasteNytt&lt;/collection&gt;
59          &lt;/filter-conf&gt;
60  </pre>
61  <p>And here is for publisher with an xpath selecting collections: </p>
62  <pre>    
63  &lt;publisher className="org.backsource.xindice.AmsterdamXindicePublisher"&gt;
64        &lt;url&gt;xmldb:xindice:///db/test&lt;/url&gt;
65        &lt;collection-xpath&gt;//Subject[@Scheme="EdrumCategory"]/@FormalName&lt;/collection-xpath&gt;
66      &lt;/publisher&gt;
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       // Create a lookup table of existing collections in the base url collection.
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          //e.printStackTrace();
118          throw e;
119 //throw new ServiceException("Could not list collections :" +e,e);
120       }finally {
121          if ( ad != null) {
122             // ALLWAYS leave it back.
123             try {                 
124                pool.leaveAdapter(ad);
125             } catch (XindiceException e) {
126                log.error("Could not leave back adapter for collection: NULL");
127             } // end of try-catch
128          } // end of if ()
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          } // end of for ()
138          
139       } // end of if ()
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       } // end of if ()
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          // Make shure we have a working input stream if we are a filter
166          // that will not be emptyed!
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                } // end of try-catch
176 
177                input = dbout.toByteArray();
178                ByteArrayInputStream ris = new ByteArrayInputStream( input );
179                message.setInputStream(ris);
180                ms = new ByteArrayInputStream( input );
181             }
182          } // end of if ()
183          
184          // First get the message
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          } // end of try-catch
194          
195          if ( doc == null) {
196             // We have nothing to work on!
197             throw new ServiceException("Could not construct document from message");
198          } // end of if ()
199          
200       } finally {
201          // Make shure we set the new input stream if we created one!
202          if ( ms !=null) {
203             message.setInputStream(ms);
204          } // end of if ()
205          
206       } // end of try-catch
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          } // end of else
229          
230       } else if (xpath != null)  {
231          List list = XPathUtil.selectNodes(doc,xpath);
232          //collections = new String[ list.size() ];
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             } // end of else
243             
244          } // end of for (Iterator  = .iterator(); .hasNext();)
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       } // end of else
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          } // end of if ()
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          } // end of if ()
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 }// AmsterdamXindiceBase