You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by ju...@apache.org on 2008/01/28 21:34:36 UTC
svn commit: r616030 - in /jackrabbit/trunk/jackrabbit-jcr-commons/src:
main/java/org/apache/jackrabbit/commons/SerializingContentHandler.java
test/java/org/apache/jackrabbit/commons/SerializingContentHandlerTest.java
Author: jukka
Date: Mon Jan 28 12:34:34 2008
New Revision: 616030
URL: http://svn.apache.org/viewvc?rev=616030&view=rev
Log:
JCR-1350: Add a serializing content handler
Added:
jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/SerializingContentHandler.java
- copied, changed from r616012, cocoon/trunk/core/cocoon-pipeline/cocoon-pipeline-impl/src/main/java/org/apache/cocoon/serialization/AbstractTextSerializer.java
jackrabbit/trunk/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/SerializingContentHandlerTest.java
Copied: jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/SerializingContentHandler.java (from r616012, cocoon/trunk/core/cocoon-pipeline/cocoon-pipeline-impl/src/main/java/org/apache/cocoon/serialization/AbstractTextSerializer.java)
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/SerializingContentHandler.java?p2=jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/SerializingContentHandler.java&p1=cocoon/trunk/core/cocoon-pipeline/cocoon-pipeline-impl/src/main/java/org/apache/cocoon/serialization/AbstractTextSerializer.java&r1=616012&r2=616030&rev=616030&view=diff
==============================================================================
--- cocoon/trunk/core/cocoon-pipeline/cocoon-pipeline-impl/src/main/java/org/apache/cocoon/serialization/AbstractTextSerializer.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/SerializingContentHandler.java Mon Jan 28 12:34:34 2008
@@ -14,447 +14,80 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.cocoon.serialization;
+package org.apache.jackrabbit.commons;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Properties;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.TransformerFactoryConfigurationError;
+
+import javax.xml.transform.Result;
+import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
-import org.apache.avalon.framework.configuration.Configurable;
-import org.apache.avalon.framework.configuration.Configuration;
-import org.apache.avalon.framework.configuration.ConfigurationException;
-import org.apache.avalon.framework.service.ServiceException;
-import org.apache.avalon.framework.service.ServiceManager;
-import org.apache.avalon.framework.service.Serviceable;
-import org.apache.commons.lang.BooleanUtils;
-import org.apache.excalibur.source.SourceValidity;
-import org.apache.excalibur.source.impl.validity.NOPValidity;
-
-import org.apache.cocoon.Constants;
-import org.apache.cocoon.caching.CacheableProcessingComponent;
-import org.apache.cocoon.configuration.Settings;
-import org.apache.cocoon.util.ClassUtils;
-import org.apache.cocoon.util.TraxErrorHandler;
-import org.apache.cocoon.xml.AbstractXMLPipe;
-import org.apache.cocoon.xml.XMLConsumer;
-import org.apache.cocoon.xml.XMLUtils;
-
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
-import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.AttributesImpl;
/**
- * @version $Id$
+ * A {@link ContentHandler} that serializes SAX events to a given
+ * {@link Result} instance. The JAXP {@link SAXTransformerFactory}
+ * facility is used for the serialization. This class handles also cases
+ * problems like the Xalan serializer in JDK 1.4 not outputting correct
+ * xmlns attributes.
*/
-public abstract class AbstractTextSerializer extends AbstractSerializer
- implements Configurable, Serviceable, CacheableProcessingComponent {
-
- /**
- * Cache for avoiding unnecessary checks of namespaces abilities.
- * It associates a Boolean to the transformer class name.
- */
- private static final Map needsNamespaceCache = new HashMap();
-
- /**
- * The trax <code>TransformerFactory</code> used by this serializer.
- */
- private SAXTransformerFactory tfactory;
-
- /**
- * The <code>Properties</code> used by this serializer.
- */
- protected Properties format = new Properties();
-
- /**
- * The pipe that adds namespaces as xmlns attributes.
- */
- private NamespaceAsAttributes namespacePipe;
-
- /**
- * The caching key
- */
- private String cachingKey;
-
- private String transformerFactoryClass;
-
- private String defaultEncoding = null;
-
- /**
- * Set the properties used for transformer handler used by the serializer
- * see {@link OutputKeys} for possible settings.
- *
- * @param format
- */
- public void setFormat(Properties format) {
- this.format = format;
- }
-
- /**
- * Set the default encoding. This will be overided if the encoding is set
- * in the format properties. This is mainly useful together with Spring
- * bean inheritance.
- *
- * @param defaultEncoding
- */
- public void setDefaultEncoding(String defaultEncoding) {
- this.defaultEncoding = defaultEncoding;
- }
-
- /**
- * Initialize logger, caching key, transformer handler and namespace pipe
- *
- * @throws Exception
- */
- public void init() throws Exception {
- if (!this.format.containsKey(OutputKeys.ENCODING) && this.defaultEncoding != null) {
- this.format.put(OutputKeys.ENCODING, this.defaultEncoding);
- }
- this.cachingKey = createCachingKey(format);
- initTransformerFactory();
- initNamespacePipe();
- }
-
- /**
- * Optionally set the transformer factory used for creating the transformer
- * handler that is used for serialization. Otherwise the standard transformer
- * factory is used ({@link TransformerFactory}).
- *
- * @param transformerFactoryClass the name of the class
- */
- public void setTransformerFactory(String transformerFactoryClass) {
- this.transformerFactoryClass = transformerFactoryClass;
- }
-
- /**
- * Interpose namespace pipe if needed.
- */
- public void setConsumer(XMLConsumer consumer) {
- if (this.namespacePipe == null) {
- super.setConsumer(consumer);
- } else {
- this.namespacePipe.setConsumer(consumer);
- super.setConsumer(this.namespacePipe);
- }
- }
-
- /**
- * Interpose namespace pipe if needed.
- */
- public void setContentHandler(ContentHandler handler) {
- if (this.namespacePipe == null) {
- super.setContentHandler(handler);
- } else {
- this.namespacePipe.setContentHandler(handler);
- super.setContentHandler(this.namespacePipe);
- }
- }
-
- /**
- * Interpose namespace pipe if needed.
- */
- public void setLexicalHandler(LexicalHandler handler) {
- if (this.namespacePipe == null) {
- super.setLexicalHandler(handler);
- } else {
- this.namespacePipe.setLexicalHandler(handler);
- super.setLexicalHandler(this.namespacePipe);
- }
- }
-
- /**
- * Helper for TransformerFactory.
- */
- protected SAXTransformerFactory getTransformerFactory() {
- return tfactory;
- }
-
- /**
- * Helper for TransformerHandler.
- */
- protected TransformerHandler getTransformerHandler() throws TransformerException {
- return this.getTransformerFactory().newTransformerHandler();
- }
-
-// /**
-// * Set the {@link OutputStream} where the requested resource should
-// * be serialized.
-// */
-// public void setOutputStream(OutputStream out) throws IOException {
-// /*
-// * Add a level of buffering to the output stream. Xalan serializes
-// * every character individually. In conjunction with chunked
-// * transfer encoding this would otherwise lead to a whopping 6-fold
-// * increase of data on the wire.
-// */
-// // if (outputBufferSize > 0) {
-// // super.setOutputStream(
-// // new BufferedOutputStream(out, outputBufferSize));
-// // } else {
-// super.setOutputStream(out);
-// // }
-// }
-
- /**
- * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
- *
- * @deprecated use property injection instead
- */
- public void service(ServiceManager manager) throws ServiceException {
- final Settings settings = (Settings)manager.lookup(Settings.ROLE);
- String defaultEncoding = settings.getFormEncoding();
- if (defaultEncoding != null) {
- this.format.setProperty(OutputKeys.ENCODING, defaultEncoding);
- }
- manager.release(settings);
- }
-
- /**
- * Set the configurations for this serializer.
- *
- * @deprecated use property injection instead
- */
- public void configure(Configuration conf) throws ConfigurationException {
- // configure buffer size
- // Configuration bsc = conf.getChild("buffer-size", false);
- // if(null != bsc)
- // outputBufferSize = bsc.getValueAsInteger(DEFAULT_BUFFER_SIZE);
-
- // configure xalan
- String cdataSectionElements = conf.getChild("cdata-section-elements").getValue(null);
- String dtPublic = conf.getChild("doctype-public").getValue(null);
- String dtSystem = conf.getChild("doctype-system").getValue(null);
- String encoding = conf.getChild("encoding").getValue(null);
- String indent = conf.getChild("indent").getValue(null);
- String mediaType = conf.getChild("media-type").getValue(null);
- String method = conf.getChild("method").getValue(null);
- String omitXMLDeclaration = conf.getChild("omit-xml-declaration").getValue(null);
- String standAlone = conf.getChild("standalone").getValue(null);
- String version = conf.getChild("version").getValue(null);
-
- if (cdataSectionElements != null) {
- format.put(OutputKeys.CDATA_SECTION_ELEMENTS, cdataSectionElements);
- }
- if (dtPublic != null) {
- format.put(OutputKeys.DOCTYPE_PUBLIC, dtPublic);
- }
- if (dtSystem != null) {
- format.put(OutputKeys.DOCTYPE_SYSTEM, dtSystem);
- }
- if (encoding != null) {
- format.put(OutputKeys.ENCODING, encoding);
- }
- if (indent != null) {
- format.put(OutputKeys.INDENT, indent);
- }
- if (mediaType != null) {
- format.put(OutputKeys.MEDIA_TYPE, mediaType);
- }
- if (method != null) {
- format.put(OutputKeys.METHOD, method);
- }
- if (omitXMLDeclaration != null) {
- format.put(OutputKeys.OMIT_XML_DECLARATION, omitXMLDeclaration);
- }
- if (standAlone != null) {
- format.put(OutputKeys.STANDALONE, standAlone);
- }
- if (version != null) {
- format.put(OutputKeys.VERSION, version);
- }
+public class SerializingContentHandler extends DefaultContentHandler {
- this.cachingKey = createCachingKey(format);
-
- this.transformerFactoryClass = conf.getChild("transformer-factory").getValue(null);
- this.initTransformerFactory();
-
- this.initNamespacePipe();
-
- }
-
- /**
- * @see org.apache.avalon.excalibur.pool.Recyclable#recycle()
- */
- public void recycle() {
- super.recycle();
- if (this.namespacePipe != null) {
- this.namespacePipe.recycle();
- }
- }
+ public static ContentHandler getSerializer(Result result)
+ throws SAXException, TransformerConfigurationException {
+ SAXTransformerFactory factory = (SAXTransformerFactory)
+ SAXTransformerFactory.newInstance();
+
+ TransformerHandler handler = factory.newTransformerHandler();
+ handler.setResult(result);
+
+ TransformerHandler probe = factory.newTransformerHandler();
+ StringWriter writer = new StringWriter();
+ probe.setResult(new StreamResult(writer));
+ probe.startDocument();
+ probe.startPrefixMapping("p", "uri");
+ probe.startElement("uri", "e", "p:e", new AttributesImpl());
+ probe.endElement("uri", "e", "p:e");
+ probe.endPrefixMapping("p");
+ probe.endDocument();
- private void initTransformerFactory() throws ConfigurationException, TransformerFactoryConfigurationError {
- if (transformerFactoryClass != null) {
- try {
- this.tfactory = (SAXTransformerFactory) ClassUtils.newInstance(transformerFactoryClass);
- if (getLogger().isDebugEnabled()) {
- getLogger().debug("Using transformer factory " + transformerFactoryClass);
- }
- } catch (Exception e) {
- throw new ConfigurationException("Cannot load transformer factory " + transformerFactoryClass, e);
- }
+ if (writer.toString().indexOf("xmlns") == -1) {
+ return new NamespaceAsAttributes(handler);
} else {
- // Standard TrAX behaviour
- this.tfactory = (SAXTransformerFactory) TransformerFactory.newInstance();
+ return handler;
}
- tfactory.setErrorListener(new TraxErrorHandler(getLogger()));
}
- private void initNamespacePipe() {
- // Check if we need namespace as attributes.
- try {
- if (needsNamespacesAsAttributes()) {
- // Setup a correction pipe
- this.namespacePipe = new NamespaceAsAttributes();
- }
- } catch (Exception e) {
- getLogger().warn("Cannot know if transformer needs namespaces attributes - assuming NO.", e);
- }
+ public SerializingContentHandler(Result result)
+ throws TransformerConfigurationException, SAXException {
+ super(getSerializer(result));
}
- /**
- * Create the caching key from the formating properties used by the
- * transformer handler that is used for serialization.
- * @param format
- *
- */
- protected static String createCachingKey(Properties format) {
- final StringBuffer buffer = new StringBuffer();
- String value = null;
-
- // Use lookup of the property values instead of just iterating through the
- // enumeration of them to give the caching key a deterministic order of its parts
-
- if ((value = format.getProperty(OutputKeys.CDATA_SECTION_ELEMENTS)) != null) {
- buffer.append(";cdata-section-elements=").append(value);
- }
- if ((value = format.getProperty(OutputKeys.DOCTYPE_PUBLIC)) != null) {
- buffer.append(";doctype-public=").append(value);
- }
- if ((value = format.getProperty(OutputKeys.DOCTYPE_SYSTEM)) != null) {
- buffer.append(";doctype-system=").append(value);
- }
- if ((value = format.getProperty(OutputKeys.ENCODING)) != null) {
- buffer.append(";encoding=").append(value);
- }
- if ((value = format.getProperty(OutputKeys.INDENT)) != null) {
- buffer.append(";indent=").append(value);
- }
- if ((value = format.getProperty(OutputKeys.MEDIA_TYPE)) != null) {
- buffer.append(";media-type=").append(value);
- }
- if ((value = format.getProperty(OutputKeys.METHOD)) != null) {
- buffer.append(";method=").append(value);
- }
- if ((value = format.getProperty(OutputKeys.OMIT_XML_DECLARATION)) != null) {
- buffer.append(";omit-xml-declaration=").append(value);
- }
- if ((value = format.getProperty(OutputKeys.STANDALONE)) != null) {
- buffer.append(";standalone=").append(value);
- }
- if ((value = format.getProperty(OutputKeys.VERSION)) != null) {
- buffer.append(";version=").append(value);
- }
-
- if ( buffer.length() > 0 )
- return buffer.toString();
- else
- return "1";
- }
-
- /**
- * Generate the unique key.
- * This key must be unique inside the space of this component.
- * This method must be invoked before the generateValidity() method.
- *
- * @return The generated key or <code>0</code> if the component
- * is currently not cacheable.
- */
- public java.io.Serializable getKey() {
- return this.cachingKey;
- }
-
- /**
- * Generate the validity object.
- * Before this method can be invoked the generateKey() method
- * must be invoked.
- *
- * @return The generated validity object or <code>null</code> if the
- * component is currently not cacheable.
- */
- public SourceValidity getValidity() {
- return NOPValidity.SHARED_INSTANCE;
- }
-
- /**
- * Checks if the used Trax implementation correctly handles namespaces set using
- * <code>startPrefixMapping()</code>, but wants them also as 'xmlns:' attributes.
- * <p>
- * The check consists in sending SAX events representing a minimal namespaced document
- * with namespaces defined only with calls to <code>startPrefixMapping</code> (no
- * xmlns:xxx attributes) and check if they are present in the resulting text.
- */
- protected boolean needsNamespacesAsAttributes() throws Exception {
-
- SAXTransformerFactory factory = getTransformerFactory();
-
- Boolean cacheValue = (Boolean) needsNamespaceCache.get(factory.getClass().getName());
- if (cacheValue != null) {
- return cacheValue.booleanValue();
- } else {
- // Serialize a minimal document to check how namespaces are handled.
- StringWriter writer = new StringWriter();
-
- String uri = "namespaceuri";
- String prefix = "nsp";
- String check = "xmlns:" + prefix + "='" + uri + "'";
-
- TransformerHandler handler = this.getTransformerHandler();
-
- handler.getTransformer().setOutputProperties(format);
- handler.setResult(new StreamResult(writer));
-
- // Output a single element
- handler.startDocument();
- handler.startPrefixMapping(prefix, uri);
- handler.startElement(uri, "element", "element", XMLUtils.EMPTY_ATTRIBUTES);
- handler.endElement(uri, "element", "element");
- handler.endPrefixMapping(prefix);
- handler.endDocument();
-
- String text = writer.toString();
-
- // Check if the namespace is there (replace " by ' to be sure of what we search in)
- boolean needsIt = (text.replace('"', '\'').indexOf(check) == -1);
-
- String msg = needsIt ? " needs namespace attributes (will be slower)." : " handles correctly namespaces.";
-
- getLogger().debug("Trax handler " + handler.getClass().getName() + msg);
-
- needsNamespaceCache.put(factory.getClass().getName(), BooleanUtils.toBooleanObject(needsIt));
-
- return needsIt;
- }
- }
-
- //--------------------------------------------------------------------------------------------
/**
* A pipe that ensures that all namespace prefixes are also present as
* 'xmlns:' attributes. This used to circumvent Xalan's serialization behaviour
* which is to ignore namespaces if they're not present as 'xmlns:xxx' attributes.
- */
- public static class NamespaceAsAttributes extends AbstractXMLPipe {
+ * <p>
+ * NOTE: This class was originally written for Apache Cocoon and is
+ * included with minor modifications here in Apache Jackrabbit. See the
+ * org.apache.cocoon.serialization.AbstractTextSerializer class in the
+ * cocoon-pipeline-impl component for the original code.
+ */
+ private static class NamespaceAsAttributes extends DefaultContentHandler {
+
+ /** The URI for xml namespaces */
+ private static final String XML_NAMESPACE_URI =
+ "http://www.w3.org/XML/1998/namespace";
/**
* The prefixes of startPrefixMapping() declarations for the coming element.
@@ -478,6 +111,10 @@
*/
private boolean hasMappings = false;
+ public NamespaceAsAttributes(ContentHandler handler) {
+ super(handler);
+ }
+
public void startDocument() throws SAXException {
// Cleanup
this.uriToPrefixMap.clear();
@@ -547,8 +184,6 @@
if (qName.equals(attrs.getQName(attr))) {
// Check if mapping and attribute URI match
if (!uri.equals(attrs.getValue(attr))) {
- getLogger().error("URI in prefix mapping and attribute do not match : '"
- + uri + "' - '" + attrs.getURI(attr) + "'");
throw new SAXException("URI in prefix mapping and attribute do not match");
}
found = true;
@@ -569,9 +204,11 @@
}
if (prefix.equals("")) {
- newAttrs.addAttribute(Constants.XML_NAMESPACE_URI, "xmlns", "xmlns", "CDATA", uri);
+ newAttrs.addAttribute(
+ XML_NAMESPACE_URI, "xmlns", "xmlns", "CDATA", uri);
} else {
- newAttrs.addAttribute(Constants.XML_NAMESPACE_URI, prefix, qName, "CDATA", uri);
+ newAttrs.addAttribute(
+ XML_NAMESPACE_URI, prefix, qName, "CDATA", uri);
}
}
} // end for mapping
@@ -646,5 +283,7 @@
this.prefixList.clear();
this.uriList.clear();
}
+
}
+
}
Added: jackrabbit/trunk/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/SerializingContentHandlerTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/SerializingContentHandlerTest.java?rev=616030&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/SerializingContentHandlerTest.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/SerializingContentHandlerTest.java Mon Jan 28 12:34:34 2008
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.commons;
+
+import java.io.StringWriter;
+
+import javax.xml.transform.stream.StreamResult;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.helpers.AttributesImpl;
+
+import junit.framework.TestCase;
+
+public class SerializingContentHandlerTest extends TestCase {
+
+ public void testSerializingContentHandler() throws Exception {
+ StringWriter writer = new StringWriter();
+
+ ContentHandler handler =
+ new SerializingContentHandler(new StreamResult(writer));
+ handler.startDocument();
+ handler.startPrefixMapping("p", "uri");
+ handler.startElement("uri", "a", "p:a", new AttributesImpl());
+ AttributesImpl attributes = new AttributesImpl();
+ attributes.addAttribute("uri", "foo", "p:foo", "CDATA", "bar");
+ handler.startElement(null, "b", "b", attributes);
+ handler.characters("abc".toCharArray(), 0, 3);
+ handler.endElement(null, "b", "b");
+ handler.startElement(null, "c", "c", new AttributesImpl());
+ handler.endElement(null, "c", "c");
+ handler.characters("xyz".toCharArray(), 0, 3);
+ handler.endElement("uri", "a", "p:a");
+ handler.endPrefixMapping("p");
+ handler.endDocument();
+
+ String xml = writer.toString();
+ assertContains(xml, "<p:a");
+ assertContains(xml, "xmlns:p");
+ assertContains(xml, "=");
+ assertContains(xml, "uri");
+ assertContains(xml, ">");
+ assertContains(xml, "<b");
+ assertContains(xml, "p:foo");
+ assertContains(xml, "bar");
+ assertContains(xml, "abc");
+ assertContains(xml, "</b>");
+ assertContains(xml, "<c/>");
+ assertContains(xml, "xyz");
+ assertContains(xml, "</p:a>");
+ }
+
+ private void assertContains(String haystack, String needle) {
+ if (haystack.indexOf(needle) == -1) {
+ fail("'" + haystack + "' does not contain '" + needle+ "'");
+ }
+ }
+}