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>
* <map:match pattern="documents/**">
* <map:generate type="xpathdirectory" src="docs/{1}">
* <map:parameter name="xpath" value="/article/title|/article/abstract"/>
+ * <map:parameter name="nsmapping" value="mapping.proeprties"/>
+ * <map:parameter name="nsmapping-reload" value="false"/>
* <map:parameter name="xmlFiles" value="\.xml$"/>
* </map:generate>
* <map:serialize type="xml" />
* </map:match>
* </pre>
*
- * <p>Request:<br>
- * http://www.some.host/documents/test</p>
- *
+ * <p>
+ * Request: <br>http://www.some.host/documents/test
+ * </p>
* Result:
* <pre>
- * <dir:directory
- * name="test" lastModified="1010400942000"
- * date="1/7/02 11:55 AM" requested="true"
- * xmlns:dir="http://apache.org/cocoon/directory/2.0">
+ * <dir:directory name="test" lastModified="1010400942000" date="1/7/02 11:55 AM" requested="true" xmlns:dir="http://apache.org/cocoon/directory/2.0">
* <dir:directory name="subdirectory" lastModified="1010400942000" date="1/7/02 11:55 AM"/>
* <dir:file name="test.xml" lastModified="1011011579000" date="1/14/02 1:32 PM">
* <dir:xpath query="/article/title">
@@ -115,81 +140,136 @@
* </dir:directory>
* </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