1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the  "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18/*
19 * $Id: SourceTreeManager.java 468655 2006-10-28 07:12:06Z minchau $
20 */
21package org.apache.xpath;
22
23import java.io.IOException;
24import java.util.Vector;
25
26import javax.xml.transform.Source;
27import javax.xml.transform.SourceLocator;
28import javax.xml.transform.TransformerException;
29import javax.xml.transform.URIResolver;
30import javax.xml.transform.sax.SAXSource;
31import javax.xml.transform.stream.StreamSource;
32
33import org.apache.xml.dtm.DTM;
34import org.apache.xml.utils.SystemIDResolver;
35
36import org.xml.sax.XMLReader;
37import org.xml.sax.helpers.XMLReaderFactory;
38
39/**
40 * This class bottlenecks all management of source trees.  The methods
41 * in this class should allow easy garbage collection of source
42 * trees (not yet!), and should centralize parsing for those source trees.
43 */
44public class SourceTreeManager
45{
46
47  /** Vector of SourceTree objects that this manager manages. */
48  private Vector m_sourceTree = new Vector();
49
50  /**
51   * Reset the list of SourceTree objects that this manager manages.
52   *
53   */
54  public void reset()
55  {
56    m_sourceTree = new Vector();
57  }
58
59  /** The TrAX URI resolver used to obtain source trees. */
60  URIResolver m_uriResolver;
61
62  /**
63   * Set an object that will be used to resolve URIs used in
64   * document(), etc.
65   * @param resolver An object that implements the URIResolver interface,
66   * or null.
67   */
68  public void setURIResolver(URIResolver resolver)
69  {
70    m_uriResolver = resolver;
71  }
72
73  /**
74   * Get the object that will be used to resolve URIs used in
75   * document(), etc.
76   * @return An object that implements the URIResolver interface,
77   * or null.
78   */
79  public URIResolver getURIResolver()
80  {
81    return m_uriResolver;
82  }
83
84  /**
85   * Given a document, find the URL associated with that document.
86   * @param owner Document that was previously processed by this liaison.
87   *
88   * @return The base URI of the owner argument.
89   */
90  public String findURIFromDoc(int owner)
91  {
92    int n = m_sourceTree.size();
93
94    for (int i = 0; i < n; i++)
95    {
96      SourceTree sTree = (SourceTree) m_sourceTree.elementAt(i);
97
98      if (owner == sTree.m_root)
99        return sTree.m_url;
100    }
101
102    return null;
103  }
104
105  /**
106   * This will be called by the processor when it encounters
107   * an xsl:include, xsl:import, or document() function.
108   *
109   * @param base The base URI that should be used.
110   * @param urlString Value from an xsl:import or xsl:include's href attribute,
111   * or a URI specified in the document() function.
112   *
113   * @return a Source that can be used to process the resource.
114   *
115   * @throws IOException
116   * @throws TransformerException
117   */
118  public Source resolveURI(
119          String base, String urlString, SourceLocator locator)
120            throws TransformerException, IOException
121  {
122
123    Source source = null;
124
125    if (null != m_uriResolver)
126    {
127      source = m_uriResolver.resolve(urlString, base);
128    }
129
130    if (null == source)
131    {
132      String uri = SystemIDResolver.getAbsoluteURI(urlString, base);
133
134      source = new StreamSource(uri);
135    }
136
137    return source;
138  }
139
140  /** JJK: Support  <?xalan:doc_cache_off?> kluge in ElemForEach.
141   * TODO: This function is highly dangerous. Cache management must be improved.
142   *
143   * @param n The node to remove.
144   */
145  public void removeDocumentFromCache(int n)
146  {
147    if(DTM.NULL ==n)
148      return;
149    for(int i=m_sourceTree.size()-1;i>=0;--i)
150    {
151      SourceTree st=(SourceTree)m_sourceTree.elementAt(i);
152      if(st!=null && st.m_root==n)
153      {
154	m_sourceTree.removeElementAt(i);
155	return;
156      }
157    }
158  }
159
160
161
162  /**
163   * Put the source tree root node in the document cache.
164   * TODO: This function needs to be a LOT more sophisticated.
165   *
166   * @param n The node to cache.
167   * @param source The Source object to cache.
168   */
169  public void putDocumentInCache(int n, Source source)
170  {
171
172    int cachedNode = getNode(source);
173
174    if (DTM.NULL != cachedNode)
175    {
176      if (!(cachedNode == n))
177        throw new RuntimeException(
178          "Programmer's Error!  "
179          + "putDocumentInCache found reparse of doc: "
180          + source.getSystemId());
181      return;
182    }
183    if (null != source.getSystemId())
184    {
185      m_sourceTree.addElement(new SourceTree(n, source.getSystemId()));
186    }
187  }
188
189  /**
190   * Given a Source object, find the node associated with it.
191   *
192   * @param source The Source object to act as the key.
193   *
194   * @return The node that is associated with the Source, or null if not found.
195   */
196  public int getNode(Source source)
197  {
198
199//    if (source instanceof DOMSource)
200//      return ((DOMSource) source).getNode();
201
202    // TODO: Not sure if the BaseID is really the same thing as the ID.
203    String url = source.getSystemId();
204
205    if (null == url)
206      return DTM.NULL;
207
208    int n = m_sourceTree.size();
209
210    // System.out.println("getNode: "+n);
211    for (int i = 0; i < n; i++)
212    {
213      SourceTree sTree = (SourceTree) m_sourceTree.elementAt(i);
214
215      // System.out.println("getNode -         url: "+url);
216      // System.out.println("getNode - sTree.m_url: "+sTree.m_url);
217      if (url.equals(sTree.m_url))
218        return sTree.m_root;
219    }
220
221    // System.out.println("getNode - returning: "+node);
222    return DTM.NULL;
223  }
224
225  /**
226   * Get the source tree from the a base URL and a URL string.
227   *
228   * @param base The base URI to use if the urlString is relative.
229   * @param urlString An absolute or relative URL string.
230   * @param locator The location of the caller, for diagnostic purposes.
231   *
232   * @return should be a non-null reference to the node identified by the
233   * base and urlString.
234   *
235   * @throws TransformerException If the URL can not resolve to a node.
236   */
237  public int getSourceTree(
238          String base, String urlString, SourceLocator locator, XPathContext xctxt)
239            throws TransformerException
240  {
241
242    // System.out.println("getSourceTree");
243    try
244    {
245      Source source = this.resolveURI(base, urlString, locator);
246
247      // System.out.println("getSourceTree - base: "+base+", urlString: "+urlString+", source: "+source.getSystemId());
248      return getSourceTree(source, locator, xctxt);
249    }
250    catch (IOException ioe)
251    {
252      throw new TransformerException(ioe.getMessage(), locator, ioe);
253    }
254
255    /* catch (TransformerException te)
256     {
257       throw new TransformerException(te.getMessage(), locator, te);
258     }*/
259  }
260
261  /**
262   * Get the source tree from the input source.
263   *
264   * @param source The Source object that should identify the desired node.
265   * @param locator The location of the caller, for diagnostic purposes.
266   *
267   * @return non-null reference to a node.
268   *
269   * @throws TransformerException if the Source argument can't be resolved to
270   *         a node.
271   */
272  public int getSourceTree(Source source, SourceLocator locator, XPathContext xctxt)
273          throws TransformerException
274  {
275
276    int n = getNode(source);
277
278    if (DTM.NULL != n)
279      return n;
280
281    n = parseToNode(source, locator, xctxt);
282
283    if (DTM.NULL != n)
284      putDocumentInCache(n, source);
285
286    return n;
287  }
288
289  /**
290   * Try to create a DOM source tree from the input source.
291   *
292   * @param source The Source object that identifies the source node.
293   * @param locator The location of the caller, for diagnostic purposes.
294   *
295   * @return non-null reference to node identified by the source argument.
296   *
297   * @throws TransformerException if the source argument can not be resolved
298   *         to a source node.
299   */
300  public int parseToNode(Source source, SourceLocator locator, XPathContext xctxt)
301          throws TransformerException
302  {
303
304    try
305    {
306      Object xowner = xctxt.getOwnerObject();
307      DTM dtm;
308      if(null != xowner && xowner instanceof org.apache.xml.dtm.DTMWSFilter)
309      {
310        dtm = xctxt.getDTM(source, false,
311                          (org.apache.xml.dtm.DTMWSFilter)xowner, false, true);
312      }
313      else
314      {
315        dtm = xctxt.getDTM(source, false, null, false, true);
316      }
317      return dtm.getDocument();
318    }
319    catch (Exception e)
320    {
321      //e.printStackTrace();
322      throw new TransformerException(e.getMessage(), locator, e);
323    }
324
325  }
326
327  /**
328   * This method returns the SAX2 parser to use with the InputSource
329   * obtained from this URI.
330   * It may return null if any SAX2-conformant XML parser can be used,
331   * or if getInputSource() will also return null. The parser must
332   * be free for use (i.e.
333   * not currently in use for another parse().
334   *
335   * @param inputSource The value returned from the URIResolver.
336   * @return a SAX2 XMLReader to use to resolve the inputSource argument.
337   * @param locator The location of the original caller, for diagnostic purposes.
338   *
339   * @throws TransformerException if the reader can not be created.
340   */
341  public static XMLReader getXMLReader(Source inputSource, SourceLocator locator)
342          throws TransformerException
343  {
344
345    try
346    {
347      XMLReader reader = (inputSource instanceof SAXSource)
348                         ? ((SAXSource) inputSource).getXMLReader() : null;
349
350      if (null == reader)
351      {
352        try {
353          javax.xml.parsers.SAXParserFactory factory=
354              javax.xml.parsers.SAXParserFactory.newInstance();
355          factory.setNamespaceAware( true );
356          javax.xml.parsers.SAXParser jaxpParser=
357              factory.newSAXParser();
358          reader=jaxpParser.getXMLReader();
359
360        } catch( javax.xml.parsers.ParserConfigurationException ex ) {
361          throw new org.xml.sax.SAXException( ex );
362        } catch( javax.xml.parsers.FactoryConfigurationError ex1 ) {
363            throw new org.xml.sax.SAXException( ex1.toString() );
364        } catch( NoSuchMethodError ex2 ) {
365        }
366        catch (AbstractMethodError ame){}
367        if(null == reader)
368          reader = XMLReaderFactory.createXMLReader();
369      }
370
371      try
372      {
373        reader.setFeature("http://xml.org/sax/features/namespace-prefixes",
374                          true);
375      }
376      catch (org.xml.sax.SAXException se)
377      {
378
379        // What can we do?
380        // TODO: User diagnostics.
381      }
382
383      return reader;
384    }
385    catch (org.xml.sax.SAXException se)
386    {
387      throw new TransformerException(se.getMessage(), locator, se);
388    }
389  }
390}
391