You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by gi...@apache.org on 2003/11/07 10:37:14 UTC

cvs commit: cocoon-2.1/src/java/org/apache/cocoon/generation XPathDirectoryGenerator.java

giacomo     2003/11/07 01:37:14

  Modified:    src/java/org/apache/cocoon/generation
                        XPathDirectoryGenerator.java
  Log:
  Allow the generator to resolve namespace prefixes to
  namespaces URIs to enable namespace dependant xpath
  queries
  
  Revision  Changes    Path
  1.5       +252 -100  cocoon-2.1/src/java/org/apache/cocoon/generation/XPathDirectoryGenerator.java
  
  Index: XPathDirectoryGenerator.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/java/org/apache/cocoon/generation/XPathDirectoryGenerator.java,v
  retrieving revision 1.4
  retrieving revision 1.5
  diff -u -d -b -u -r1.4 -r1.5
  --- XPathDirectoryGenerator.java	23 Sep 2003 22:46:44 -0000	1.4
  +++ XPathDirectoryGenerator.java	7 Nov 2003 09:37:13 -0000	1.5
  @@ -50,58 +50,83 @@
   */
   package org.apache.cocoon.generation;
   
  +import java.io.BufferedReader;
   import java.io.File;
   import java.io.IOException;
  +import java.io.InputStreamReader;
  +import java.util.HashMap;
   import java.util.Map;
   
  +import org.apache.avalon.framework.logger.Logger;
   import org.apache.avalon.framework.parameters.Parameters;
   import org.apache.avalon.framework.service.ServiceException;
   import org.apache.avalon.framework.service.ServiceManager;
   import org.apache.cocoon.ProcessingException;
  +
   import org.apache.cocoon.components.source.SourceUtil;
  +
   import org.apache.cocoon.environment.SourceResolver;
  +
   import org.apache.cocoon.xml.dom.DOMStreamer;
  +
   import org.apache.excalibur.source.Source;
  +import org.apache.excalibur.source.SourceNotFoundException;
  +
   import org.apache.excalibur.xml.dom.DOMParser;
  +import org.apache.excalibur.xml.xpath.PrefixResolver;
   import org.apache.excalibur.xml.xpath.XPathProcessor;
  +
   import org.apache.regexp.RE;
   import org.apache.regexp.RESyntaxException;
  +
   import org.w3c.dom.Document;
   import org.w3c.dom.NodeList;
  +
   import org.xml.sax.SAXException;
  +
   import org.xml.sax.helpers.AttributesImpl;
   
  +
   /**
  - * Generates an XML directory listing performing XPath queries on XML files. It
  - * can be used both as a plain DirectoryGenerator or, by specifying a parameter
  - * <code>xpath</code>, it will perform an XPath query on every XML resource.
  - * Therefore an additional parameter <code>xmlFiles</code> can be set in the
  - * sitemap setting the regular expression pattern for determining if a file
  - * should be handled as XML file or not. The default value for this param is
  - * <code>\.xml$</code>, so that it matches all files ending <code>.xml</code>.
  - * <br>
  - * Sample usage:<br>
  - * <br>
  - * Sitemap:
  + * <p>
  + * Generates an XML directory listing performing XPath queries on XML files. It can be used both as a plain
  + * DirectoryGenerator or, by specifying a parameter <code>xpath</code>, it will perform an XPath query on every XML
  + * resource. A <code>nsmapping</code> parameter can be specified to point to a file containing lines to map prefixes
  + * to namespaces like this:
  + * </p>
  + * <p>
  + *  prefix=namespace-uri<br/>
  + *  prefix2=namespace-uri-2
  + * </p>
  + * <p>
  + * A parameter <code>nsmapping-reload</code> specifies if the prefix-2-namespace mapping file should be checked to be
  + * reloaded on each request to this generator if it was modified since the last time it was read. 
  + * </p>
  + * <p>
  + * An additional parameter <code>xmlFiles</code> can be set in the sitemap setting the regular expression pattern for 
  + * determining if a file should be handled as XML file or not. The default value for this param is 
  + * <code>\.xml$</code>, so that it * matches all files ending <code>.xml</code>.
  + * 
  + * <p></p>
  + * <br>Sample usage: <br><br>Sitemap:
    * <pre>
    * &lt;map:match pattern="documents/**"&gt;
    *   &lt;map:generate type="xpathdirectory" src="docs/{1}"&gt;
    *     &lt;map:parameter name="xpath" value="/article/title|/article/abstract"/&gt;
  + *    &lt;map:parameter name="nsmapping" value="mapping.proeprties"/&gt; 
  + *    &lt;map:parameter name="nsmapping-reload" value="false"/&gt; 
    *     &lt;map:parameter name="xmlFiles" value="\.xml$"/&gt;
    *   &lt;/map:generate&gt;
    *   &lt;map:serialize type="xml" /&gt;
    * &lt;/map:match&gt;
    * </pre>
    *
  - * <p>Request:<br>
  - *   http://www.some.host/documents/test</p>
  - *
  + * <p>
  + * Request: <br>http://www.some.host/documents/test
  + * </p>
    * Result:
    * <pre>
  - * &lt;dir:directory
  - *   name="test" lastModified="1010400942000"
  - *   date="1/7/02 11:55 AM" requested="true"
  - *   xmlns:dir="http://apache.org/cocoon/directory/2.0"&gt;
  + *  &lt;dir:directory name="test" lastModified="1010400942000" date="1/7/02 11:55 AM" requested="true" xmlns:dir="http://apache.org/cocoon/directory/2.0"&gt; 
    *   &lt;dir:directory name="subdirectory" lastModified="1010400942000" date="1/7/02 11:55 AM"/&gt;
    *   &lt;dir:file name="test.xml" lastModified="1011011579000" date="1/14/02 1:32 PM"&gt;
    *     &lt;dir:xpath query="/article/title"&gt;
  @@ -115,81 +140,136 @@
    * &lt;/dir:directory&gt;
    * </pre>
    *
  + * @author <a href="mailto:giacomo@apache.org">Giacomo Pati</a>
    * @author <a href="mailto:gianugo@apache.org">Gianugo Rabellino</a>
    * @author <a href="mailto:joerg@apache.org">J�rg Heinicke</a>
    * @version CVS $Id$
    */
  -public class XPathDirectoryGenerator extends DirectoryGenerator {
  -
  +public class XPathDirectoryGenerator
  +extends DirectoryGenerator {
       /** Local name for the element that contains the included XML snippet. */
       protected static final String XPATH_NODE_NAME = "xpath";
  +
       /** Attribute for the XPath query. */
       protected static final String QUERY_ATTR_NAME = "query";
   
  -    /** The regular expression for the XML files pattern. */
  -    protected RE xmlRE = null;
  +    /** All the mapping files lastmodified dates */
  +    protected static Map mappingFiles = new HashMap();
  +
  +    /** The parser for the XML snippets to be included. */
  +    protected DOMParser parser = null;
  +
       /** The document that should be parsed and (partly) included. */
       protected Document doc = null;
  +
  +    /** The PrefixResolver responsable for processing current request (if any). */
  +    protected PrefixResolver prefixResolver = null;
  +
  +    /** The regular expression for the XML files pattern. */
  +    protected RE xmlRE = null;
  +
       /** The XPath. */
       protected String xpath = null;
  +
       /** The XPath processor. */
       protected XPathProcessor processor = null;
  -    /** The parser for the XML snippets to be included. */
  -    protected DOMParser parser = null;
   
  +    /**
  +     * Disposable
  +     */
  +    public void dispose() {
  +        if (this.manager != null) {
  +            this.manager.release(this.processor);
  +            this.manager.release(this.parser);
  +            this.processor = null;
  +            this.parser = null;
  +        }
  +
  +        super.dispose();
  +    }
  +
  +    /**
  +     * Recycle resources
  +     */
  +    public void recycle() {
  +        this.xpath = null;
  +        this.doc = null;
  +
  +        //this.parser = null;
  +        //this.processor = null;
  +        super.recycle();
  +    }
  +
  +    /**
  +     * Serviceable
  +     *
  +     * @param manager the ComponentManager
  +     *
  +     * @throws ServiceException in case a component could not be found
  +     */
  +    public void service(ServiceManager manager)
  +    throws ServiceException {
  +        super.service(manager);
  +        this.processor = (XPathProcessor)manager.lookup(XPathProcessor.ROLE);
  +        this.parser = (DOMParser)manager.lookup(DOMParser.ROLE);
  +    }
  +
  +    /**
  +     * Setup this sitemap component
  +     *
  +     * @param resolver the SourceResolver
  +     * @param objectModel The environmental object model
  +     * @param src the source attribute
  +     * @param par the parameters
  +     *
  +     * @throws ProcessingException if processing failes
  +     * @throws SAXException in case of XML related errors
  +     * @throws IOException in case of file related errors
  +     */
       public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par)
               throws ProcessingException, SAXException, IOException {
           super.setup(resolver, objectModel, src, par);
  +
           // See if an XPath was specified
           this.xpath = par.getParameter("xpath", null);
           this.cacheKeyParList.add(this.xpath);
  +
           if (getLogger().isDebugEnabled()) {
  -            getLogger().debug("Applying XPath: " + this.xpath +
  -                              " to directory " + this.source);
  +            getLogger().debug("Applying XPath: " + this.xpath + " to directory " + this.source);
  +        }
  +
  +        final String mappings = par.getParameter("nsmapping", null);
  +
  +        if (null != mappings) {
  +            final boolean mapping_reload =
  +                Boolean.valueOf(par.getParameterAsBoolean("nsmapping-reload", false)).booleanValue();
  +            final Source mappingSource = resolver.resolveURI(mappings);
  +            final String mappingKey = mappingSource.getURI();
  +            final MappingInfo mappingInfo = (MappingInfo)XPathDirectoryGenerator.mappingFiles.get(mappingKey);
  +
  +            if ((null == mappingInfo) || (mappingInfo.reload == false) ||
  +                (mappingInfo.mappingSource.getLastModified() < mappingSource.getLastModified())) {
  +                this.prefixResolver = new MappingInfo(getLogger().getChildLogger( "prefix-resolver" ), mappingSource, mapping_reload);
  +                XPathDirectoryGenerator.mappingFiles.put(mappingKey, this.prefixResolver);
  +            }
  +            else
  +            {
  +            	this.prefixResolver = mappingInfo;
           }
  +        }
  +
           String xmlFilesPattern = null;
  +
           try {
               xmlFilesPattern = par.getParameter("xmlFiles", "\\.xml$");
               this.cacheKeyParList.add(xmlFilesPattern);
               this.xmlRE = new RE(xmlFilesPattern);
  +
               if (getLogger().isDebugEnabled()) {
                   getLogger().debug("pattern for XML files: " + xmlFilesPattern);
               }
           } catch (RESyntaxException rese) {
  -            throw new ProcessingException("Syntax error in regexp pattern '"
  -                                          + xmlFilesPattern + "'", rese);
  -        }
  -    }
  -
  -    /**
  -     * Serviceable
  -     */
  -    public void service(ServiceManager manager) throws ServiceException {
  -        super.service(manager);
  -        this.processor = (XPathProcessor)manager.lookup(XPathProcessor.ROLE);
  -        this.parser = (DOMParser)manager.lookup(DOMParser.ROLE);
  -    }
  -
  -    /**
  -     * Disposable
  -     */
  -    public void dispose() {
  -        if ( this.manager != null ) {
  -            this.manager.release( this.processor );
  -            this.manager.release( this.parser );
  -            this.processor = null;
  -            this.parser = null;
  -        }
  -        super.dispose();
  -    }
  -    /**
  -     * Extends the startNode() method of the DirectoryGenerator by starting
  -     * a possible XPath query on a file.
  -     */
  -    protected void startNode(String nodeName, File path) throws SAXException {
  -        super.startNode(nodeName, path);
  -        if (this.xpath != null && path.isFile() && this.isXML(path)) {
  -            performXPathQuery(path);
  +            throw new ProcessingException("Syntax error in regexp pattern '" + xmlFilesPattern + "'", rese);
           }
       }
   
  @@ -197,6 +277,7 @@
        * Determines if a given File shall be handled as XML.
        *
        * @param path  the File to check
  +     *
        * @return true  if the given File shall handled as XML, false otherwise.
        */
       protected boolean isXML(File path) {
  @@ -205,49 +286,120 @@
   
       /**
        * Performs an XPath query on the file.
  +     *
        * @param xmlFile  the File the XPath is performed on.
  +     *
        * @throws SAXException  if something goes wrong while adding the XML snippet.
        */
  -    protected void performXPathQuery(File xmlFile) throws SAXException {
  +    protected void performXPathQuery(File xmlFile)
  +    throws SAXException {
           this.doc = null;
  +
           Source source = null;
  +
           try {
               source = resolver.resolveURI(xmlFile.toURL().toExternalForm());
               this.doc = this.parser.parseDocument(SourceUtil.getInputSource(source));
           } catch (SAXException e) {
  -            getLogger().error("Warning:" + xmlFile.getName() +
  -                              " is not a valid XML file. Ignoring.", e);
  +            getLogger().error("Warning:" + xmlFile.getName() + " is not a valid XML file. Ignoring.", e);
           } catch (ProcessingException e) {
  -            getLogger().error("Warning: Problem while reading the file " +
  -                              xmlFile.getName() + ". Ignoring.", e);
  +            getLogger().error("Warning: Problem while reading the file " + xmlFile.getName() + ". Ignoring.", e);
           } catch (IOException e) {
  -            getLogger().error("Warning: Problem while reading the file " +
  -                              xmlFile.getName() + ". Ignoring.", e);
  +            getLogger().error("Warning: Problem while reading the file " + xmlFile.getName() + ". Ignoring.", e);
           } finally {
               resolver.release(source);
           }
   
           if (doc != null) {
  -            NodeList nl = this.processor.selectNodeList(this.doc.getDocumentElement(), this.xpath);
  +            NodeList nl = this.processor.selectNodeList(this.doc.getDocumentElement(), this.xpath,this.prefixResolver);
               AttributesImpl attributes = new AttributesImpl();
               attributes.addAttribute("", QUERY_ATTR_NAME, QUERY_ATTR_NAME, "CDATA", xpath);
               super.contentHandler.startElement(URI, XPATH_NODE_NAME, PREFIX + ":" + XPATH_NODE_NAME, attributes);
  +
               DOMStreamer ds = new DOMStreamer(super.xmlConsumer);
  +
               for (int i = 0; i < nl.getLength(); i++) {
                   ds.stream(nl.item(i));
               }
  +
               super.contentHandler.endElement(URI, XPATH_NODE_NAME, PREFIX + ":" + XPATH_NODE_NAME);
           }
       }
   
       /**
  -     * Recycle resources
  +     * Extends the startNode() method of the DirectoryGenerator by starting a possible XPath query on a file.
  +     *
  +     * @param nodeName the node currently processing
  +     * @param path the file path
  +     *
  +     * @throws SAXException in case of errors
        */
  -    public void recycle() {
  -        this.xpath = null;
  -        this.doc = null;
  -        //this.parser = null;
  -        //this.processor = null;
  -        super.recycle();
  +    protected void startNode(String nodeName, File path)
  +    throws SAXException {
  +        super.startNode(nodeName, path);
  +
  +        if ((this.xpath != null) && path.isFile() && this.isXML(path)) {
  +            performXPathQuery(path);
  +        }
  +    }
  +
  +    /**
  +     * The MappingInfo class to reolve namespace prefixes to their namespace URI
  +     *
  +     * @author <a href="mailto:giacomo(at)apache.org">Giacomo Pati</a>
  +     * @version CVS $Id$
  +     */
  +    private static class MappingInfo
  +    implements PrefixResolver {
  +        /** The Source of the mapping file */
  +        public final Source mappingSource;
  +
  +        /** Whether to reload if mapping file has changed */
  +        public final boolean reload;
  +
  +        /** Map of prefixes to namespaces */
  +        private final Map prefixMap;
  +
  +        /** Our Logger */
  +        private final Logger logger;
  +
  +        /**
  +         * Creates a new MappingInfo object.
  +         *
  +         * @param mappingSource The Source of the mapping file
  +         * @param reload Whether to reload if mapping file has changed
  +         *
  +         * @throws SourceNotFoundException In case the mentioned source is not there
  +         * @throws IOException in case the source could not be read
  +         */
  +        public MappingInfo(final Logger logger, final Source mappingSource, final boolean reload)
  +        throws SourceNotFoundException, IOException {
  +            this.logger = logger;
  +            this.mappingSource = mappingSource;
  +            this.reload = reload;
  +            prefixMap = new HashMap();
  +
  +            final BufferedReader br = new BufferedReader(new InputStreamReader(mappingSource.getInputStream()));
  +
  +            for (String line = br.readLine(); line != null; line = br.readLine()) {
  +                final int i = line.indexOf('=');
  +
  +                if (i > 0) {
  +                    final String prefix = line.substring(0, i);
  +                    final String namespace = line.substring(i + 1);
  +                    prefixMap.put(prefix, namespace);
  +                    logger.debug( "added mapping: '" + prefix + "'='" + namespace + "'" );
  +                }
  +            }
  +        }
  +
  +        /* (non-Javadoc)
  +         * @see org.apache.excalibur.xml.xpath.PrefixResolver#prefixToNamespace(java.lang.String)
  +         */
  +        public String prefixToNamespace(String prefix) {
  +            final String namespace = (String)this.prefixMap.get(prefix);
  +            logger.debug( "have to resolve prefix='" + prefix + ", found namespace='" + namespace + "'" );
  +            return namespace;
  +        }
       }
   }
  
  
  

Re: cvs commit: cocoon-2.1/src/java/org/apache/cocoon/generation XPathDirectoryGenerator.java

Posted by Vadim Gritsenko <va...@verizon.net>.
Giacomo Pati wrote:

> Vadim Gritsenko wrote:
>
>> giacomo@apache.org wrote:
>>
>>>  + * resource. A <code>nsmapping</code> parameter can be specified 
>>> to point to a file containing lines to map prefixes
>>>  + * to namespaces like this:
>>>  + * </p>
>>>  + * <p>
>>>  + *  prefix=namespace-uri<br/>
>>>  + *  prefix2=namespace-uri-2
>>>  + * </p> 
>>
>>
>> Just a quick one -- why configuration is not used, but properties file?
>
>
> I couldn't think of an elegant way to parametrize more that one 
> prefix/namespace pair as
>
>  <map:parameter name="prefix.1" value="p"/>
>  <map:parameter namespace.1" value="http://....."/>
>  <map:parameter name="prefix.2" value="q"/>
>  <map:parameter namespace.2" value="http://....."/>
>
> wasn't really elegant. But if you have a good idea just throw it in :-)


Two ideas:

1) Use configure():
    <namespace prefix="wd">http://apache.org/cocoon/woody/...</namespace>
2) Seen in recent commit in some other component:
    <map:parameter name="namespace.wd" 
value="http://apache.org/cocoon/woody/..."/>

Is it better? :)

Vadim


Re: cvs commit: cocoon-2.1/src/java/org/apache/cocoon/generation XPathDirectoryGenerator.java

Posted by Giacomo Pati <gi...@apache.org>.

Vadim Gritsenko wrote:
> giacomo@apache.org wrote:
> 
>>  + * resource. A <code>nsmapping</code> parameter can be specified to 
>> point to a file containing lines to map prefixes
>>  + * to namespaces like this:
>>  + * </p>
>>  + * <p>
>>  + *  prefix=namespace-uri<br/>
>>  + *  prefix2=namespace-uri-2
>>  + * </p>
>>  
>>
> 
> Just a quick one -- why configuration is not used, but properties file?

I couldn't think of an elegant way to parametrize more that one 
prefix/namespace pair as

  <map:parameter name="prefix.1" value="p"/>
  <map:parameter namespace.1" value="http://....."/>
  <map:parameter name="prefix.2" value="q"/>
  <map:parameter namespace.2" value="http://....."/>

wasn't really elegant. But if you have a good idea just throw it in :-)


-- 
Giacomo Pati
Otego AG, Switzerland - http://www.otego.com
Orixo, the XML business alliance - http://www.orixo.com



Re: cvs commit: cocoon-2.1/src/java/org/apache/cocoon/generation XPathDirectoryGenerator.java

Posted by Vadim Gritsenko <va...@verizon.net>.
giacomo@apache.org wrote:

>  + * resource. A <code>nsmapping</code> parameter can be specified to point to a file containing lines to map prefixes
>  + * to namespaces like this:
>  + * </p>
>  + * <p>
>  + *  prefix=namespace-uri<br/>
>  + *  prefix2=namespace-uri-2
>  + * </p>
>  
>

Just a quick one -- why configuration is not used, but properties file?

Vadim