View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors.
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   * 
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.jcr;
25  
26  import java.io.IOException;
27  import java.io.OutputStream;
28  import java.util.Arrays;
29  import java.util.Collection;
30  import java.util.HashMap;
31  import java.util.List;
32  import java.util.Map;
33  import javax.jcr.ItemVisitor;
34  import javax.jcr.NamespaceRegistry;
35  import javax.jcr.Node;
36  import javax.jcr.RepositoryException;
37  import net.jcip.annotations.NotThreadSafe;
38  import org.modeshape.common.text.TextEncoder;
39  import org.modeshape.common.text.XmlNameEncoder;
40  import org.modeshape.common.xml.StreamingContentHandler;
41  import org.modeshape.graph.property.Name;
42  import org.xml.sax.Attributes;
43  import org.xml.sax.ContentHandler;
44  import org.xml.sax.SAXException;
45  
46  /**
47   * Superclass of ModeShape JCR exporters, provides basic support for traversing through the nodes recursively (if needed),
48   * exception wrapping (since {@link ItemVisitor} does not allow checked exceptions to be thrown from its visit* methods, and the
49   * ability to wrap an {@link OutputStream} with a {@link ContentHandler}. <p /> Each exporter is only intended to be used once (by
50   * calling <code>exportView</code>) and discarded. This class is <b>NOT</b> thread-safe.
51   * 
52   * @see JcrSystemViewExporter
53   * @see JcrDocumentViewExporter
54   */
55  @NotThreadSafe
56  abstract class AbstractJcrExporter {
57  
58      /**
59       * Encoder to properly escape XML names.
60       * 
61       * @see XmlNameEncoder
62       */
63      private static final TextEncoder NAME_ENCODER = new XmlNameEncoder();
64  
65      /**
66       * The list of XML namespaces that are predefined and should not be exported by the content handler.
67       */
68      private static final List<String> UNEXPORTABLE_NAMESPACES = Arrays.asList(new String[] {"", "xml", "xmlns"});
69  
70      /**
71       * The session in which this exporter was created.
72       */
73      protected final JcrSession session;
74  
75      /**
76       * The list of XML namespace prefixes that should never be exported.
77       */
78      private final Collection<String> restrictedPrefixes;
79  
80      /**
81       * Cache from {@link Name}s to their rewritten version based on session uri mappings.
82       */
83      private final Map<Name, String> prefixedNames;
84  
85      /**
86       * Creates the exporter
87       * 
88       * @param session the session in which the exporter is created
89       * @param restrictedPrefixes the list of XML namespace prefixes that should not be exported
90       */
91      AbstractJcrExporter( JcrSession session,
92                           Collection<String> restrictedPrefixes ) {
93          this.session = session;
94          this.restrictedPrefixes = restrictedPrefixes;
95          this.prefixedNames = new HashMap<Name, String>();
96      }
97  
98      /**
99       * Returns the &quot;prefixed&quot; or rewritten version of <code>baseName</code> based on the URI mappings in the current
100      * session. For example:</p> If the namespace &quot;http://www.example.com/JCR/example/1.0&quot; is mapped to the prefix
101      * &quot;foo&quot; in the current session (or as a persistent mapping that has not been re-mapped in the current session),
102      * this method will return the string &quot;foo:bar&quot; when passed a {@link Name} with uri
103      * &quot;http://www.example.com/JCR/example/1.0&quot; and local name &quot;bar&quot;.</p> This method does manage and utilize
104      * a {@link Name} to {@link String} cache at the instance scope.
105      * 
106      * @param baseName the name to be re-mapped into its prefixed version
107      * @return the prefixed version of <code>baseName</code> based on the current session URI mappings (which include all
108      *         persistent URI mappings by default).
109      * @see #prefixedNames
110      * @see javax.jcr.Session#setNamespacePrefix(String, String)
111      * @see javax.jcr.Session#getNamespacePrefix(String)
112      */
113     protected String getPrefixedName( Name baseName ) {
114         String prefixedName = prefixedNames.get(baseName);
115 
116         if (prefixedName == null) {
117             prefixedName = baseName.getString(session.getExecutionContext().getNamespaceRegistry());
118 
119             prefixedNames.put(baseName, prefixedName);
120         }
121 
122         return prefixedName;
123     }
124 
125     /**
126      * Exports <code>node</code> (or the subtree rooted at <code>node</code>) into an XML document by invoking SAX events on
127      * <code>contentHandler</code>.
128      * 
129      * @param exportRootNode the node which should be exported. If <code>noRecursion</code> was set to <code>false</code> in the
130      *        constructor, the entire subtree rooted at <code>node</code> will be exported.
131      * @param contentHandler the SAX content handler for which SAX events will be invoked as the XML document is created.
132      * @param skipBinary if <code>true</code>, indicates that binary properties should not be exported
133      * @param noRecurse if<code>true</code>, indicates that only the given node should be exported, otherwise a recursice export
134      *        and not any of its child nodes.
135      * @throws SAXException if an exception occurs during generation of the XML document
136      * @throws RepositoryException if an exception occurs accessing the content repository
137      */
138     public void exportView( Node exportRootNode,
139                             ContentHandler contentHandler,
140                             boolean skipBinary,
141                             boolean noRecurse ) throws RepositoryException, SAXException {
142         assert exportRootNode != null;
143         assert contentHandler != null;
144         session.checkLive();
145 
146         // Export the namespace mappings used in this session
147         NamespaceRegistry registry = session.getWorkspace().getNamespaceRegistry();
148 
149         contentHandler.startDocument();
150         String[] namespacePrefixes = registry.getPrefixes();
151         for (int i = 0; i < namespacePrefixes.length; i++) {
152             String prefix = namespacePrefixes[i];
153 
154             if (!restrictedPrefixes.contains(prefix)) {
155                 contentHandler.startPrefixMapping(prefix, registry.getURI(prefix));
156             }
157         }
158 
159         exportNode(exportRootNode, contentHandler, skipBinary, noRecurse);
160 
161         for (int i = 0; i < namespacePrefixes.length; i++) {
162             if (!restrictedPrefixes.contains(namespacePrefixes[i])) {
163                 contentHandler.endPrefixMapping(namespacePrefixes[i]);
164             }
165         }
166 
167         contentHandler.endDocument();
168     }
169 
170     /**
171      * Exports <code>node</code> (or the subtree rooted at <code>node</code>) into an XML document that is written to
172      * <code>os</code>.
173      * 
174      * @param node the node which should be exported. If <code>noRecursion</code> was set to <code>false</code> in the
175      *        constructor, the entire subtree rooted at <code>node</code> will be exported.
176      * @param os the {@link OutputStream} to which the XML document will be written
177      * @param skipBinary if <code>true</code>, indicates that binary properties should not be exported
178      * @param noRecurse if<code>true</code>, indicates that only the given node should be exported, otherwise a recursive export
179      *        and not any of its child nodes.
180      * @throws RepositoryException if an exception occurs accessing the content repository, generating the XML document, or
181      *         writing it to the output stream <code>os</code>.
182      */
183     public void exportView( Node node,
184                             OutputStream os,
185                             boolean skipBinary,
186                             boolean noRecurse ) throws RepositoryException {
187         try {
188             exportView(node, new StreamingContentHandler(os, UNEXPORTABLE_NAMESPACES), skipBinary, noRecurse);
189             os.flush();
190         } catch (IOException ioe) {
191             throw new RepositoryException(ioe);
192         } catch (SAXException se) {
193             throw new RepositoryException(se);
194         }
195     }
196 
197     /**
198      * Exports <code>node</code> (or the subtree rooted at <code>node</code>) into an XML document by invoking SAX events on
199      * <code>contentHandler</code>.
200      * 
201      * @param node the node which should be exported. If <code>noRecursion</code> was set to <code>false</code> in the
202      *        constructor, the entire subtree rooted at <code>node</code> will be exported.
203      * @param contentHandler the SAX content handler for which SAX events will be invoked as the XML document is created.
204      * @param skipBinary if <code>true</code>, indicates that binary properties should not be exported
205      * @param noRecurse if<code>true</code>, indicates that only the given node should be exported, otherwise a recursive export
206      *        and not any of its child nodes.
207      * @throws SAXException if an exception occurs during generation of the XML document
208      * @throws RepositoryException if an exception occurs accessing the content repository
209      */
210     public abstract void exportNode( Node node,
211                                      ContentHandler contentHandler,
212                                      boolean skipBinary,
213                                      boolean noRecurse ) throws RepositoryException, SAXException;
214 
215     /**
216      * Convenience method to invoke the {@link ContentHandler#startElement(String, String, String, Attributes)} method on the
217      * given content handler. The name will be encoded to properly escape invalid XML characters.
218      * 
219      * @param contentHandler the content handler on which the <code>startElement</code> method should be invoked.
220      * @param name the un-encoded, un-prefixed name of the element to start
221      * @param atts the attributes that should be created for the given element
222      * @throws SAXException if there is an error starting the element
223      */
224     protected void startElement( ContentHandler contentHandler,
225                                  Name name,
226                                  Attributes atts ) throws SAXException {
227         contentHandler.startElement(name.getNamespaceUri(),
228                                     NAME_ENCODER.encode(name.getLocalName()),
229                                     NAME_ENCODER.encode(getPrefixedName(name)),
230                                     atts);
231     }
232 
233     /**
234      * Convenience method to invoke the {@link ContentHandler#endElement(String, String, String)} method on the given content
235      * handler. The name will be encoded to properly escape invalid XML characters.
236      * 
237      * @param contentHandler the content handler on which the <code>endElement</code> method should be invoked.
238      * @param name the un-encoded, un-prefixed name of the element to end
239      * @throws SAXException if there is an error ending the element
240      */
241     protected void endElement( ContentHandler contentHandler,
242                                Name name ) throws SAXException {
243         contentHandler.endElement(name.getNamespaceUri(),
244                                   NAME_ENCODER.encode(name.getLocalName()),
245                                   NAME_ENCODER.encode(getPrefixedName(name)));
246     }
247 }