| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. 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.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> |
| * |
| * @see ItemVisitor |
| * @see Session#exportSystemView(String, ContentHandler, boolean, boolean) |
| * @see Session#exportSystemView(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 value the given value |
| * @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 e) { |
| throw new RepositoryException(e); |
| } |
| } |
| |
| /** |
| * 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"); |
| } |
| |
| } |