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 2005/04/16 07:46:26 UTC
svn commit: r161553 [4/4] - in
incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org: ./ apache/
apache/jackrabbit/ apache/jackrabbit/base/ apache/jackrabbit/base/nodetype/
apache/jackrabbit/decorator/ apache/jackrabbit/iterator/
apache/jackrabbit/lite/ apache/jackrabbit/lite/nodetype/
apache/jackrabbit/name/ apache/jackrabbit/trace/ apache/jackrabbit/xml/
Added: incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/DocumentViewExportVisitor.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/DocumentViewExportVisitor.java?view=auto&rev=161553
==============================================================================
--- incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/DocumentViewExportVisitor.java (added)
+++ incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/DocumentViewExportVisitor.java Fri Apr 15 22:46:17 2005
@@ -0,0 +1,502 @@
+/*
+ * Copyright 2004-2005 The Apache Software Foundation or its licensors,
+ * as applicable.
+ *
+ * Licensed 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.xml;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.jcr.Item;
+import javax.jcr.ItemVisitor;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.jackrabbit.name.Name;
+import org.apache.xerces.util.XMLChar;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * Generic document view exporter for JCR content repositories.
+ * This class can be used to implement the XML document view export
+ * operations using nothing but the standard JCR interfaces. The
+ * export operation is implemented as an ItemVisitor that generates
+ * the document view SAX event stream as it traverses the selected
+ * JCR content tree.
+ *
+ * <h2>Implementing a customized XML serializer</h2>
+ * <p>
+ * A client can extend this class to provide customized XML serialization
+ * formats. By overriding the protected includeProperty() and includeNode()
+ * methods, a subclass can select which properties and nodes will be included
+ * in the serialized XML stream.
+ * <p>
+ * For example, the following code implements an XML serialization that only
+ * contains the titles of the first two levels of the node tree.
+ * <pre>
+ * ContentHandler handler = ...;
+ * Node parent = ...;
+ * parent.accept(
+ * new DocumentViewExportVisitor(handler, true, false) {
+ *
+ * protected boolean includeProperty(Property property)
+ * throws RepositoryException {
+ * if (property.getName().equals("title")) {
+ * return super.includeProperty(property);
+ * } else {
+ * return false;
+ * }
+ * }
+ *
+ * protected boolean includeNode(Node node)
+ * throws RepositoryException {
+ * return (node.getDepth() <= root.getDepth() + 2);
+ * }
+ *
+ * });
+ * </pre>
+ *
+ * <h2>Implementing the standard export methods</h2>
+ * <p>
+ * The following is an example of the
+ * Session.exportDocView(String, ContentHandler, boolean, boolean)
+ * method implemented in terms of this exporter class:
+ * <pre>
+ * public void exportDocView(String absPath, ContentHandler handler,
+ * boolean skipBinary, boolean noRecurse) throws
+ * InvalidSerializedDataException, PathNotFoundException,
+ * SAXException, RepositoryException {
+ * Item item = getItem(absPath);
+ * if (item.isNode()) {
+ * item.accept(new DocumentViewExportVisitor(
+ * handler, skipBinary, noRecurse));
+ * } else {
+ * throw new PathNotFoundException("Invalid node path: " + path);
+ * }
+ * }
+ * </pre>
+ * <p>
+ * The companion method
+ * Session.exportDocView(String, OutputStream, boolean, boolean)
+ * can be implemented in terms of the above method and the XMLSerializer
+ * class from the Xerces library:
+ * <pre>
+ * import org.apache.xml.serialize.XMLSerializer;
+ * import org.apache.xml.serialize.OutputFormat;
+ *
+ * public void exportDocView(String absPath, OutputStream output,
+ * boolean skipBinary, boolean noRecurse) throws
+ * InvalidSerializedDataException, PathNotFoundException,
+ * IOException, RepositoryException {
+ * try {
+ * XMLSerializer serializer =
+ * new XMLSerializer(output, new OutputFormat());
+ * exportDocView(absPath, serializer.asContentHandler(),
+ * binaryAsLink, noRecurse);
+ * } catch (SAXException ex) {
+ * throw new IOException(ex.getMessage());
+ * }
+ * }
+ * </pre>
+ *
+ * @author Jukka Zitting
+ * @see ItemVisitor
+ * @see Session#exportDocView(String, ContentHandler, boolean, boolean)
+ * @see Session#exportDocView(String, java.io.OutputStream, boolean, boolean)
+ */
+public class DocumentViewExportVisitor implements ItemVisitor {
+
+ /** The special jcr:xmltext property name. */
+ private static final String XMLTEXT = "jcr:xmltext";
+
+ /** The special jcr:xmlcharacters property name. */
+ private static final String XMLCHARACTERS = "jcr:xmlcharacters";
+
+ /**
+ * The SAX content handler for the serialized XML stream.
+ */
+ private ContentHandler handler;
+
+ /**
+ * Flag to skip all binary properties.
+ */
+ private boolean skipBinary;
+
+ /**
+ * Flag to only serialize the selected node.
+ */
+ private boolean noRecurse;
+
+ /**
+ * The root node of the serialization tree. This is the node that
+ * is mapped to the root element of the serialized XML stream.
+ */
+ protected Node root;
+
+ /**
+ * Creates an visitor for exporting content using the document view
+ * format. To actually perform the export operation, you need to pass
+ * the visitor instance to the selected content node using the
+ * Node.accept(ItemVisitor) method.
+ *
+ * @param handler the SAX event handler
+ * @param skipBinary flag for ignoring binary properties
+ * @param noRecurse flag for not exporting an entire content subtree
+ */
+ public DocumentViewExportVisitor(
+ ContentHandler handler, boolean skipBinary, boolean noRecurse) {
+ this.handler = handler;
+ this.skipBinary = skipBinary;
+ this.noRecurse = noRecurse;
+ this.root = null;
+ }
+
+ /**
+ * Ignored. Properties are included as attributes of node elements.
+ * {@inheritDoc}
+ */
+ public void visit(Property property) throws RepositoryException {
+ }
+
+ /**
+ * Exports the visited node using the document view serialization format.
+ * This method is the main entry point to the serialization mechanism.
+ * It manages the opening and closing of the SAX event stream and the
+ * registration of the namespace mappings. The process of actually
+ * generating the document view SAX events is spread into various
+ * private methods, and can be controlled by overriding the protected
+ * includeProperty() and includeNode() methods.
+ *
+ * @param node the node to visit
+ * @throws RepositoryException on repository errors
+ */
+ public void visit(Node node) throws RepositoryException {
+ try {
+ // start document
+ if (root == null) {
+ root = node;
+ handler.startDocument();
+
+ Session session = root.getSession();
+ String[] prefixes = session.getNamespacePrefixes();
+ for (int i = 0; i < prefixes.length; i++) {
+ handler.startPrefixMapping(prefixes[i],
+ session.getNamespaceURI(prefixes[i]));
+ }
+ }
+
+ // export current node
+ if (!node.getName().equals(XMLTEXT)) {
+ exportNode(node);
+ } else if (node != root) {
+ exportText(node);
+ } else {
+ throw new RepositoryException("Cannot export jcr:xmltext");
+ }
+
+ // end document
+ if (root == node) {
+ handler.endDocument();
+ }
+ } catch (SAXException ex) {
+ throw new RepositoryException(ex);
+ }
+ }
+
+ /**
+ * Checks whether the given property should be included in the XML
+ * serialization. By default this method returns false for the special
+ * "jcr:xmlcharacters" properties and for binary properties if the
+ * skipBinary flag is set. Subclasses can extend this behaviour to
+ * implement more selective XML serialization.
+ * <p>
+ * To avoid losing the default behaviour described above, subclasses
+ * should always call super.includeProperty(property) instead of
+ * simply returning true for a property.
+ *
+ * @param property the property to check
+ * @return true if the property should be included, false otherwise
+ * @throws RepositoryException on repository errors
+ */
+ protected boolean includeProperty(Property property)
+ throws RepositoryException {
+ return !property.getName().equals(XMLCHARACTERS)
+ && (!skipBinary || property.getType() != PropertyType.BINARY);
+ }
+
+ /**
+ * Checks whether the given node should be included in the XML
+ * serialization. This method returns true by default, but subclasses
+ * can extend this behaviour to implement selective XML serialization.
+ * <p>
+ * Note that this method is only called for the descendants of the
+ * root node of the serialized tree. Also, this method is never called
+ * if the noRecurse flag is set because no descendant nodes will be
+ * serialized anyway.
+ *
+ * @param node the node to check
+ * @return true if the node should be included, false otherwise
+ * @throws RepositoryException on repository errors
+ */
+ protected boolean includeNode(Node node) throws RepositoryException {
+ return true;
+ }
+
+ /**
+ * Serializes a special "jcr:xmltext" node. Only the contents of the
+ * "jcr:xmlcharacters" property will be written as characters to the
+ * XML stream and no elements or attributes will be generated for
+ * this node or any other child nodes or properties.
+ *
+ * @param node the "jcr:xmltext" node
+ * @throws SAXException on SAX errors
+ * @throws RepositoryException on repository errors
+ */
+ private void exportText(Node node)
+ throws SAXException, RepositoryException {
+ try {
+ Property property = node.getProperty(XMLCHARACTERS);
+ char[] characters = property.getString().toCharArray();
+ handler.characters(characters, 0, characters.length);
+ } catch (PathNotFoundException ex) {
+ // ignore empty jcr:xmltext nodes
+ } catch (ValueFormatException ex) {
+ // ignore non-string jcr:xmlcharacters properties
+ }
+ }
+
+ /**
+ * Serializes the given node to the XML stream. Generates an element
+ * with the same name as this node, and maps node properties to
+ * attributes of the generated element. If the noRecurse flag is false,
+ * then child nodes are serialized as sub-elements.
+ *
+ * @param node the given node
+ * @throws SAXException on SAX errors
+ * @throws RepositoryException on repository errors
+ */
+ private void exportNode(Node node)
+ throws SAXException, RepositoryException {
+ Name qname = getQName(node);
+ String localName = escapeName(qname.getLocalPart());
+ String prefixedName =
+ node.getSession().getNamespacePrefix(qname.getNamespaceURI())
+ + ":" + localName;
+
+ // Start element
+ handler.startElement(
+ qname.getNamespaceURI(), localName, prefixedName,
+ getAttributes(node));
+
+ // Visit child nodes (unless denied by the noRecurse flag)
+ if (!noRecurse) {
+ NodeIterator children = node.getNodes();
+ while (children.hasNext()) {
+ Node child = children.nextNode();
+ if (includeNode(child)) {
+ child.accept(this);
+ }
+ }
+ }
+
+ // End element
+ handler.endElement(
+ qname.getNamespaceURI(), qname.getLocalPart(), node.getName());
+ }
+
+ /**
+ * Returns the document view attributes of the given Node. The
+ * properties of the node are mapped to XML attributes directly as
+ * name-value pairs.
+ *
+ * @param node the given node
+ * @return document view attributes of the node
+ * @throws RepositoryException on repository errors
+ */
+ private Attributes getAttributes(Node node) throws RepositoryException {
+ AttributesImpl attributes = new AttributesImpl();
+
+ PropertyIterator properties = node.getProperties();
+ while (properties.hasNext()) {
+ Property property = properties.nextProperty();
+ if (includeProperty(property)) {
+ Name qname = getQName(property);
+ String value = getValue(property);
+ attributes.addAttribute(qname.getNamespaceURI(),
+ qname.getLocalPart(), property.getName(),
+ "CDATA", value);
+ }
+ }
+
+ return attributes;
+ }
+
+ /**
+ * Returns the qualified XML name of the given item. If the item name
+ * is prefixed, then the respective namespace URI is looked up from the
+ * session and returned as a part of the qualified name. If the item
+ * name is not prefixed, then the returned qualified name will use the
+ * null namespace and the default prefix. The local part of the qualified
+ * name will be escaped using ISO 9075 rules. If the given item is the
+ * root node, then the special name "jcr:root" is returned
+ * <p>
+ * See section 6.4.2 of the JCR specification for more details about
+ * name handling in the XML document view serialization.
+ *
+ * @param item the given item
+ * @return qualified XML name of the item
+ * @throws RepositoryException on repository errors
+ * @see Name
+ */
+ private Name getQName(Item item) throws RepositoryException {
+ String name = item.getName();
+ if (name.length() == 0) {
+ name = "jcr:root";
+ }
+ return Name.parseJCRName(item.getSession(), name);
+ }
+
+ /**
+ * Escapes the given name or prefix according to the rules of section
+ * 6.4.3 of the JSR 170 specification (version 0.16.2).
+ *
+ * @param name original name or prefix
+ * @return escaped name or prefix
+ */
+ private String escapeName(String name) {
+ if (name.length() == 0) {
+ return name;
+ }
+
+ StringBuffer buffer = new StringBuffer();
+
+ // First character
+ if (!XMLChar.isNameStart(name.charAt(0)) || name.startsWith("_x")
+ || (name.length() >= 3
+ && "xml".equalsIgnoreCase(name.substring(0, 3)))) {
+ String hex =
+ "000" + Integer.toHexString(name.charAt(0)).toUpperCase();
+ buffer.append("_x" + hex.substring(hex.length() - 4) + "_");
+ } else {
+ buffer.append(name.charAt(0));
+ }
+
+ // Rest of the characters
+ for (int i = 1; i < name.length(); i++) {
+ if (!XMLChar.isName(name.charAt(i)) || name.startsWith("_x", i)) {
+ String hex =
+ "000" + Integer.toHexString(name.charAt(0)).toUpperCase();
+ buffer.append("_x" + hex.substring(hex.length() - 4) + "_");
+ } else {
+ buffer.append(name.charAt(i));
+ }
+ }
+
+ return buffer.toString();
+ }
+
+ /**
+ * Escapes the given value according to the rules of section 6.4.4 of
+ * the JSR 170 specification (version 0.16.2).
+ *
+ * @param value original value
+ * @return escaped value
+ */
+ private String escapeValue(String value) {
+ StringBuffer buffer = new StringBuffer();
+
+ for (int i = 1; i < value.length(); i++) {
+ if (value.charAt(i) == ' ') {
+ buffer.append("_x0020_");
+ } else if (value.startsWith("_x", i)) {
+ buffer.append("_x005F_");
+ } else {
+ buffer.append(value.charAt(i));
+ }
+ }
+
+ return buffer.toString();
+ }
+
+ /**
+ * Returns the document view representation of the given property.
+ * Multiple values are combined into a space-separated list of
+ * space-escaped string values, binary values are encoded using the
+ * Base64 encoding, and other values are simply returned using their
+ * default string representation.
+ *
+ * @param property the given property
+ * @return document view representation of the property value
+ * @throws RepositoryException on repository errors
+ */
+ private String getValue(Property property) throws RepositoryException {
+ try {
+ if (property.getDefinition().isMultiple()) {
+ StringBuffer buffer = new StringBuffer();
+
+ Value[] values = property.getValues();
+ for (int i = 0; i < values.length; i++) {
+ if (i > 0) {
+ buffer.append(" ");
+ }
+ if (values[i].getType() == PropertyType.BINARY) {
+ buffer.append(encodeValue(values[i].getStream()));
+ } else {
+ buffer.append(escapeValue(values[i].getString()));
+ }
+ }
+
+ return buffer.toString();
+ } else if (property.getType() == PropertyType.BINARY) {
+ return encodeValue(property.getStream());
+ } else {
+ return property.getString();
+ }
+ } catch (IOException ex) {
+ throw new RepositoryException(ex);
+ }
+ }
+
+ /**
+ * Encodes the given binary stream using Base64 encoding.
+ *
+ * @param input original binary value
+ * @return Base64-encoded value
+ * @throws IOException on IO errors
+ */
+ private String encodeValue(InputStream input) throws IOException {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ byte[] bytes = new byte[4096];
+ for (int n = input.read(bytes); n != -1; n = input.read(bytes)) {
+ buffer.write(bytes, 0, n);
+ }
+ return
+ new String(Base64.encodeBase64(buffer.toByteArray()), "US-ASCII");
+ }
+
+}
Propchange: incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/DocumentViewExportVisitor.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/DocumentViewImportContentHandler.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/DocumentViewImportContentHandler.java?view=auto&rev=161553
==============================================================================
--- incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/DocumentViewImportContentHandler.java (added)
+++ incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/DocumentViewImportContentHandler.java Fri Apr 15 22:46:17 2005
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2005 The Apache Software Foundation or its licensors,
+ * as applicable.
+ *
+ * Licensed 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.xml;
+
+import java.util.Stack;
+
+import javax.jcr.NamespaceException;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+/**
+ * TODO
+ */
+public class DocumentViewImportContentHandler implements ContentHandler {
+
+ private Session session;
+
+ private Stack stack;
+
+ private Node node;
+
+ private StringBuffer text;
+
+ public DocumentViewImportContentHandler(Node parent) throws RepositoryException {
+ this.session = parent.getSession();
+ this.stack = new Stack();
+ this.node = parent;
+ this.text = new StringBuffer();
+ }
+
+
+ private String getName(String uri, String local, String qname)
+ throws RepositoryException {
+ if (uri == null || uri.length() == 0) {
+ return local;
+ }
+
+ try {
+ return session.getNamespacePrefix(uri) + ":" + local;
+ } catch (NamespaceException ex) {
+ // fall through
+ }
+
+ int i = qname.indexOf(':');
+ String prefix = (i != -1) ? qname.substring(0, i) : "ext";
+ try {
+ String base = prefix;
+ for (int no = 1; true; prefix = base + no++) {
+ session.getNamespaceURI(prefix);
+ }
+ } catch (NamespaceException ex) {
+ // fall through
+ }
+
+ session.getWorkspace().getNamespaceRegistry()
+ .registerNamespace(prefix, uri);
+ return getName(uri, local, qname);
+ }
+
+ public void startElement(String uri, String localName, String qName,
+ Attributes atts) throws SAXException {
+ try {
+ importText();
+
+ stack.push(node);
+
+ node = node.addNode(getName(uri, localName, qName));
+ for (int i = 0; i < atts.getLength(); i++) {
+ String name = getName(atts.getURI(i), atts.getLocalName(i),
+ atts.getQName(i));
+ node.setProperty(name, atts.getValue(i));
+ }
+ } catch (RepositoryException ex) {
+ throw new SAXException(ex);
+ }
+ }
+
+ /**
+ * TODO
+ * {@inheritDoc}
+ */
+ public void endElement(String uri, String localName, String qName)
+ throws SAXException {
+ try {
+ importText();
+
+ node = (Node) stack.pop();
+ } catch (RepositoryException ex) {
+ throw new SAXException(ex);
+ }
+ }
+
+ /**
+ * Appends the received characters to the current text buffer.
+ * The accumulated contents of the text buffer is written to an
+ * jcr:xmltext node when an element boundary is reached.
+ * {@inheritDoc}
+ */
+ public void characters(char[] ch, int start, int length)
+ throws SAXException {
+ text.append(ch, start, length);
+ }
+
+ /**
+ * Imports the accumulated XML character data as an jcr:xmltext node.
+ * The character data is stored as a jcr:xmlcharacters string property
+ * of the created node. The character data buffer is then cleared.
+ * <p>
+ * This method does nothing if the character data buffer is empty, and
+ * can therefore be invoked whenever an element boundary is reached to
+ * handle the importing of any accumulated character data.
+ *
+ * @throws RepositoryException
+ */
+ private void importText() throws RepositoryException {
+ if (text.length() > 0) {
+ Node xmltext = node.addNode("jcr:xmltext");
+ xmltext.setProperty("jcr:xmlcharacters", text.toString());
+ text.setLength(0);
+ }
+ }
+
+ private String unescapeName(String name) {
+ StringBuffer buffer = new StringBuffer();
+ for (int i = name.indexOf("_x"); i != -1; i = name.indexOf("_x")) {
+ buffer.append(name.substring(0, i));
+ if (i + 6 < name.length() && name.charAt(i + 6) == '_') {
+ try {
+ buffer.append(
+ (char) Integer.parseInt(name.substring(2, 6), 16));
+ name = name.substring(i + 7);
+ } catch (NumberFormatException ex) {
+ buffer.append(name.charAt(i));
+ name = name.substring(i + 1);
+ }
+ } else {
+ buffer.append(name.charAt(i));
+ name = name.substring(i + 1);
+ }
+ }
+ buffer.append(name);
+ return buffer.toString();
+ }
+
+
+ /** Ignored. */
+ public void setDocumentLocator(Locator locator) {
+ }
+
+ /** Ignored. */
+ public void startDocument() {
+ }
+
+ /** Ignored. */
+ public void endDocument() throws SAXException {
+ }
+
+ /** Ignored. */
+ public void startPrefixMapping(String prefix, String uri)
+ throws SAXException {
+ }
+
+ /** Ignored. */
+ public void endPrefixMapping(String prefix) throws SAXException {
+ }
+
+ /** Ignored. */
+ public void ignorableWhitespace(char[] ch, int start, int length)
+ throws SAXException {
+ }
+
+ /** Ignored. */
+ public void processingInstruction(String target, String data)
+ throws SAXException {
+ }
+
+ /** Ignored. */
+ public void skippedEntity(String name) throws SAXException {
+ }
+
+}
Propchange: incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/DocumentViewImportContentHandler.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/SystemViewExportVisitor.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/SystemViewExportVisitor.java?view=auto&rev=161553
==============================================================================
--- incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/SystemViewExportVisitor.java (added)
+++ incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/SystemViewExportVisitor.java Fri Apr 15 22:46:17 2005
@@ -0,0 +1,430 @@
+/*
+ * Copyright 2005 The Apache Software Foundation or its licensors,
+ * as applicable.
+ *
+ * Licensed 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.xml;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.jcr.ItemVisitor;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+
+import org.apache.commons.codec.binary.Base64;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * Generic system view exporter for JCR content repositories.
+ * This class can be used to implement the XML system view export
+ * operations using nothing but the standard JCR interfaces. The
+ * export operation is implemented as an ItemVisitor that generates
+ * the system view SAX event stream as it traverses the selected
+ * JCR content tree.
+ *
+ * <h2>Implementing a customized XML serializer</h2>
+ * <p>
+ * A client can extend this class to provide customized XML serialization
+ * formats. By overriding the protected includeProperty() and includeNode()
+ * methods, a subclass can select which properties and nodes will be included
+ * in the serialized XML stream.
+ * <p>
+ * For example, the following code implements an XML serialization that only
+ * contains the titles of the first two levels of the node tree.
+ * <pre>
+ * ContentHandler handler = ...;
+ * Node parent = ...;
+ * parent.accept(
+ * new SystemViewExportVisitor(handler, true, false) {
+ *
+ * protected boolean includeProperty(Property property)
+ * throws RepositoryException {
+ * if (property.getName().equals("title")) {
+ * return super.includeProperty(property);
+ * } else {
+ * return false;
+ * }
+ * }
+ *
+ * protected boolean includeNode(Node node)
+ * throws RepositoryException {
+ * return (node.getDepth() <= root.getDepth() + 2);
+ * }
+ *
+ * });
+ * </pre>
+ *
+ * <h2>Implementing the standard export methods</h2>
+ * <p>
+ * The following is an example of the
+ * Session.exportSysView(String, ContentHandler, boolean, boolean)
+ * method implemented in terms of this exporter class:
+ * <pre>
+ * public void exportSysView(String absPath, ContentHandler handler,
+ * boolean skipBinary, boolean noRecurse) throws
+ * InvalidSerializedDataException, PathNotFoundException,
+ * SAXException, RepositoryException {
+ * Item item = getItem(absPath);
+ * if (item.isNode()) {
+ * item.accept(new DocumentViewExportVisitor(
+ * handler, skipBinary, noRecurse));
+ * } else {
+ * throw new PathNotFoundException("Invalid node path: " + path);
+ * }
+ * }
+ * </pre>
+ * <p>
+ * The companion method
+ * Session.exportSysView(String, OutputStream, boolean, boolean)
+ * can be implemented in terms of the above method and the XMLSerializer
+ * class from the Xerces library:
+ * <pre>
+ * import org.apache.xml.serialize.XMLSerializer;
+ * import org.apache.xml.serialize.OutputFormat;
+ *
+ * public void exportSysView(String absPath, OutputStream output,
+ * boolean skipBinary, boolean noRecurse) throws
+ * InvalidSerializedDataException, PathNotFoundException,
+ * IOException, RepositoryException {
+ * try {
+ * XMLSerializer serializer =
+ * new XMLSerializer(output, new OutputFormat());
+ * exportSysView(absPath, serializer.asContentHandler(),
+ * binaryAsLink, noRecurse);
+ * } catch (SAXException ex) {
+ * throw new IOException(ex.getMessage());
+ * }
+ * }
+ * </pre>
+ *
+ * @author Jukka Zitting
+ * @see ItemVisitor
+ * @see Session#exportSysView(String, ContentHandler, boolean, boolean)
+ * @see Session#exportSysView(String, java.io.OutputStream, boolean, boolean)
+ */
+public class SystemViewExportVisitor implements ItemVisitor {
+
+ /** The system view namespace URI. */
+ private static final String SV = "http://www.jcp.org/jcr/sv/1.0";
+
+ /** The special jcr:root node name. */
+ private static final String JCR_ROOT = "jcr:root";
+
+ /** The special jcr:uuid property name. */
+ private static final String JCR_UUID = "jcr:uuid";
+
+ /** The special jcr:primaryType property name. */
+ private static final String JCR_MIXINTYPES = "jcr:mixinTypes";
+
+ /** The special jcr:mixinTypes property name. */
+ private static final String JCR_PRIMARYTYPE = "jcr:primaryType";
+
+ /** The special sv:node element name. */
+ private static final String SV_NODE = "sv:node";
+
+ /** Local part of the special sv:node element name. */
+ private static final String NODE = "node";
+
+ /** The special sv:value element name. */
+ private static final String SV_VALUE = "sv:value";
+
+ /** Local part of the special sv:value element name. */
+ private static final String VALUE = "value";
+
+ /** The special sv:property element name. */
+ private static final String SV_PROPERTY = "sv:property";
+
+ /** Local part of the special sv:property element name. */
+ private static final String PROPERTY = "property";
+
+ /** The special sv:type element name. */
+ private static final String SV_TYPE = "sv:type";
+
+ /** Local part of the special sv:type element name. */
+ private static final String TYPE = "type";
+
+ /** The special sv:name element name. */
+ private static final String SV_NAME = "sv:name";
+
+ /** Local part of the special sv:name element name. */
+ private static final String NAME = "name";
+
+ /**
+ * The SAX content handler for the serialized XML stream.
+ */
+ private ContentHandler handler;
+
+ /**
+ * Flag to skip all binary properties.
+ */
+ private boolean skipBinary;
+
+ /**
+ * Flag to only serialize the selected node.
+ */
+ private boolean noRecurse;
+
+ /**
+ * The root node of the serialization tree. This is the node that
+ * is mapped to the root element of the serialized XML stream.
+ */
+ protected Node root;
+
+ /**
+ * Creates an visitor for exporting content using the system view
+ * format. To actually perform the export operation, you need to pass
+ * the visitor instance to the selected content node using the
+ * Node.accept(ItemVisitor) method.
+ *
+ * @param handler the SAX event handler
+ * @param skipBinary flag for ignoring binary properties
+ * @param noRecurse flag for not exporting an entire content subtree
+ */
+ public SystemViewExportVisitor(
+ ContentHandler handler, boolean skipBinary, boolean noRecurse) {
+ this.handler = handler;
+ this.skipBinary = skipBinary;
+ this.noRecurse = noRecurse;
+ this.root = null;
+ }
+
+ /**
+ * Exports the visited property using the system view serialization
+ * format. This method generates an sv:property element with appropriate
+ * sv:name and sv:type attributes. The value or values of the node
+ * are included as sv:value sub-elements.
+ *
+ * @param property the visited property
+ * @throws RepositoryException on repository errors
+ */
+ public void visit(Property property) throws RepositoryException {
+ try {
+ AttributesImpl attributes = new AttributesImpl();
+ attributes.addAttribute(SV, NAME, SV_NAME,
+ "CDATA", property.getName());
+ attributes.addAttribute(SV, TYPE, SV_TYPE,
+ "CDATA", PropertyType.nameFromValue(property.getType()));
+ handler.startElement(SV, PROPERTY, SV_PROPERTY, attributes);
+
+ if (property.getDefinition().isMultiple()) {
+ Value[] values = property.getValues();
+ for (int i = 0; i < values.length; i++) {
+ exportValue(values[i]);
+ }
+ } else {
+ exportValue(property.getValue());
+ }
+
+ handler.endElement(SV, PROPERTY, SV_PROPERTY);
+ } catch (SAXException ex) {
+ throw new RepositoryException(ex);
+ }
+ }
+
+ /**
+ * Exports the visited node using the system view serialization format.
+ * This method is the main entry point to the serialization mechanism.
+ * It manages the opening and closing of the SAX event stream and the
+ * registration of the namespace mappings. The process of actually
+ * generating the document view SAX events is spread into various
+ * private methods, and can be controlled by overriding the protected
+ * includeProperty() and includeNode() methods.
+ *
+ * @param node the node to visit
+ * @throws RepositoryException on repository errors
+ */
+ public void visit(Node node) throws RepositoryException {
+ try {
+ // start document
+ if (root == null) {
+ root = node;
+ handler.startDocument();
+
+ Session session = root.getSession();
+ String[] prefixes = session.getNamespacePrefixes();
+ for (int i = 0; i < prefixes.length; i++) {
+ handler.startPrefixMapping(prefixes[i],
+ session.getNamespaceURI(prefixes[i]));
+ }
+ }
+
+ // export current node
+ exportNode(node);
+
+ // end document
+ if (root == node) {
+ handler.endDocument();
+ }
+ } catch (SAXException ex) {
+ throw new RepositoryException(ex);
+ }
+ }
+
+ /**
+ * Checks whether the given property should be included in the XML
+ * serialization. By default this method returns false for the special
+ * jcr:primaryType, jcr:mixinTypes, and jcr:uuid properties that are
+ * required by the system view format. This method also returns false
+ * for all binary properties if the skipBinary flag is set.
+ * Subclasses can extend this default behaviour to implement more
+ * selective XML serialization.
+ * <p>
+ * To avoid losing the default behaviour described above, subclasses
+ * should always call super.includeProperty(property) instead of
+ * simply returning true for a property.
+ *
+ * @param property the property to check
+ * @return true if the property should be included, false otherwise
+ * @throws RepositoryException on repository errors
+ */
+ protected boolean includeProperty(Property property)
+ throws RepositoryException {
+ String name = property.getName();
+ return !name.equals(JCR_PRIMARYTYPE)
+ && !name.equals(JCR_MIXINTYPES)
+ && !name.equals(JCR_UUID)
+ && (!skipBinary || property.getType() != PropertyType.BINARY);
+ }
+
+ /**
+ * Checks whether the given node should be included in the XML
+ * serialization. This method returns true by default, but subclasses
+ * can extend this behaviour to implement selective XML serialization.
+ * <p>
+ * Note that this method is only called for the descendants of the
+ * root node of the serialized tree. Also, this method is never called
+ * if the noRecurse flag is set because no descendant nodes will be
+ * serialized anyway.
+ *
+ * @param node the node to check
+ * @return true if the node should be included, false otherwise
+ * @throws RepositoryException on repository errors
+ */
+ protected boolean includeNode(Node node) throws RepositoryException {
+ return true;
+ }
+
+ /**
+ * Serializes the given node to the XML stream. This method generates
+ * an sv:node element that contains the node name as the sv:name
+ * attribute. Node properties are included as sv:property elements
+ * and child nodes as other sv:node sub-elements.
+ *
+ * @param node the given node
+ * @throws SAXException on SAX errors
+ * @throws RepositoryException on repository errors
+ */
+ private void exportNode(Node node)
+ throws SAXException, RepositoryException {
+ String name = node.getName();
+ if (name.length() == 0) {
+ name = JCR_ROOT;
+ }
+
+ // Start element
+ AttributesImpl attributes = new AttributesImpl();
+ attributes.addAttribute(SV, NAME, SV_NAME, "CDATA", name);
+ handler.startElement(SV, NODE, SV_NODE, attributes);
+
+ // Visit the meta-properties
+ node.getProperty(JCR_PRIMARYTYPE).accept(this);
+ if (node.hasProperty(JCR_MIXINTYPES)) {
+ node.getProperty(JCR_MIXINTYPES).accept(this);
+ }
+ if (node.hasProperty(JCR_UUID)) {
+ node.getProperty(JCR_UUID).accept(this);
+ }
+
+ // Visit other properties
+ PropertyIterator properties = node.getProperties();
+ while (properties.hasNext()) {
+ Property property = properties.nextProperty();
+ if (includeProperty(property)) {
+ property.accept(this);
+ }
+ }
+
+ // Visit child nodes (unless denied by the noRecurse flag)
+ if (!noRecurse) {
+ NodeIterator children = node.getNodes();
+ while (children.hasNext()) {
+ Node child = children.nextNode();
+ if (includeNode(child)) {
+ child.accept(this);
+ }
+ }
+ }
+
+ // End element
+ handler.endElement(SV, NODE, SV_NODE);
+ }
+
+ /**
+ * Serializes the given value to the XML stream. This method generates
+ * an sv:value element and writes the string representation of the
+ * given value as the character content of the element. Binary values
+ * are encoded using the Base64 encoding.
+ *
+ * @param node the given node
+ * @throws SAXException on SAX errors
+ * @throws RepositoryException on repository errors
+ */
+ private void exportValue(Value value)
+ throws SAXException, RepositoryException {
+ try {
+ handler.startElement(SV, VALUE, SV_VALUE, new AttributesImpl());
+
+ if (value.getType() != PropertyType.BINARY) {
+ char[] characters = value.getString().toCharArray();
+ handler.characters(characters, 0, characters.length);
+ } else {
+ char[] characters =
+ encodeValue(value.getStream()).toCharArray();
+ handler.characters(characters, 0, characters.length);
+ }
+
+ handler.endElement(SV, VALUE, SV_VALUE);
+ } catch (IOException ex) {
+ throw new RepositoryException(ex);
+ }
+ }
+
+ /**
+ * Encodes the given binary stream using Base64 encoding.
+ *
+ * @param input original binary value
+ * @return Base64-encoded value
+ * @throws IOException on IO errors
+ */
+ private String encodeValue(InputStream input) throws IOException {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ byte[] bytes = new byte[4096];
+ for (int n = input.read(bytes); n != -1; n = input.read(bytes)) {
+ buffer.write(bytes, 0, n);
+ }
+ return
+ new String(Base64.encodeBase64(buffer.toByteArray()), "US-ASCII");
+ }
+
+}
Propchange: incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/SystemViewExportVisitor.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/SystemViewImportContentHandler.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/SystemViewImportContentHandler.java?view=auto&rev=161553
==============================================================================
--- incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/SystemViewImportContentHandler.java (added)
+++ incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/SystemViewImportContentHandler.java Fri Apr 15 22:46:17 2005
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2005 The Apache Software Foundation or its licensors,
+ * as applicable.
+ *
+ * Licensed 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.xml;
+
+import java.util.List;
+import java.util.Stack;
+
+import javax.jcr.BooleanValue;
+import javax.jcr.NamespaceException;
+import javax.jcr.Node;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+/**
+ * TODO
+ */
+public class SystemViewImportContentHandler implements ContentHandler {
+
+ private Session session;
+
+ private Stack stack;
+
+ private Node node;
+
+ private StringBuffer text;
+
+ private int type;
+
+ private List values;
+
+ public SystemViewImportContentHandler(Node parent) throws RepositoryException {
+ this.session = parent.getSession();
+ this.stack = new Stack();
+ this.node = parent;
+ this.text = new StringBuffer();
+ }
+
+ private String getName(String uri, String local, String qname)
+ throws RepositoryException {
+ if (uri == null || uri.length() == 0) {
+ return local;
+ }
+
+ try {
+ return session.getNamespacePrefix(uri) + ":" + local;
+ } catch (NamespaceException ex) {
+ // fall through
+ }
+
+ int i = qname.indexOf(':');
+ String prefix = (i != -1) ? qname.substring(0, i) : "ext";
+ try {
+ String base = prefix;
+ for (int no = 1; true; prefix = base + no++) {
+ session.getNamespaceURI(prefix);
+ }
+ } catch (NamespaceException ex) {
+ // fall through
+ }
+
+ session.getWorkspace().getNamespaceRegistry()
+ .registerNamespace(prefix, uri);
+ return getName(uri, local, qname);
+ }
+
+ public void startElement(String uri, String localName, String qName,
+ Attributes atts) throws SAXException {
+ try {
+ importText();
+
+ stack.push(node);
+
+ node = node.addNode(getName(uri, localName, qName));
+ for (int i = 0; i < atts.getLength(); i++) {
+ String name = getName(atts.getURI(i), atts.getLocalName(i),
+ atts.getQName(i));
+ node.setProperty(name, atts.getValue(i));
+ }
+ } catch (RepositoryException ex) {
+ throw new SAXException(ex);
+ }
+ }
+
+ /**
+ * TODO
+ * {@inheritDoc}
+ */
+ public void endElement(String uri, String localName, String qName)
+ throws SAXException {
+ if (uri.equals("SV") && localName.equals("value")) {
+ String value = text.toString();
+ switch (type) {
+ case PropertyType.BINARY:
+ // TODO values.add(new BinaryValue());
+ break;
+ case PropertyType.BOOLEAN:
+ values.add(new BooleanValue(Boolean.valueOf(value)));
+ break;
+ case PropertyType.DATE:
+ values.add(new BooleanValue(Boolean.valueOf(value)));
+ break;
+ case PropertyType.DOUBLE:
+ values.add(new BooleanValue(Boolean.valueOf(value)));
+ break;
+ case PropertyType.LONG:
+ values.add(new BooleanValue(Boolean.valueOf(value)));
+ break;
+ case PropertyType.NAME:
+ values.add(new BooleanValue(Boolean.valueOf(value)));
+ break;
+ case PropertyType.PATH:
+ values.add(new BooleanValue(Boolean.valueOf(value)));
+ break;
+ case PropertyType.REFERENCE:
+ values.add(new BooleanValue(Boolean.valueOf(value)));
+ break;
+ case PropertyType.STRING:
+ values.add(new BooleanValue(Boolean.valueOf(value)));
+ break;
+ default:
+ }
+
+ text.setLength(0);
+ }
+ try {
+ importText();
+
+ node = (Node) stack.pop();
+ } catch (RepositoryException ex) {
+ throw new SAXException(ex);
+ }
+ }
+
+ /**
+ * Appends the received characters to the current text buffer.
+ * The accumulated contents of the text buffer is written to an
+ * jcr:xmltext node when an element boundary is reached.
+ * {@inheritDoc}
+ */
+ public void characters(char[] ch, int start, int length)
+ throws SAXException {
+ text.append(ch, start, length);
+ }
+
+ /**
+ * Imports the accumulated XML character data as an jcr:xmltext node.
+ * The character data is stored as a jcr:xmlcharacters string property
+ * of the created node. The character data buffer is then cleared.
+ * <p>
+ * This method does nothing if the character data buffer is empty, and
+ * can therefore be invoked whenever an element boundary is reached to
+ * handle the importing of any accumulated character data.
+ *
+ * @throws RepositoryException
+ */
+ private void importText() throws RepositoryException {
+ if (text.length() > 0) {
+ Node xmltext = node.addNode("jcr:xmltext");
+ xmltext.setProperty("jcr:xmlcharacters", text.toString());
+ text.setLength(0);
+ }
+ }
+
+ /** Ignored. */
+ public void setDocumentLocator(Locator locator) {
+ }
+
+ /** Ignored. */
+ public void startDocument() {
+ }
+
+ /** Ignored. */
+ public void endDocument() throws SAXException {
+ }
+
+ /** Ignored. */
+ public void startPrefixMapping(String prefix, String uri)
+ throws SAXException {
+ }
+
+ /** Ignored. */
+ public void endPrefixMapping(String prefix) throws SAXException {
+ }
+
+ /** Ignored. */
+ public void ignorableWhitespace(char[] ch, int start, int length)
+ throws SAXException {
+ }
+
+ /** Ignored. */
+ public void processingInstruction(String target, String data)
+ throws SAXException {
+ }
+
+ /** Ignored. */
+ public void skippedEntity(String name) throws SAXException {
+ }
+
+}
Propchange: incubator/jackrabbit/trunk/contrib/jcr-ext/src/java/org/apache/jackrabbit/xml/SystemViewImportContentHandler.java
------------------------------------------------------------------------------
svn:eol-style = native