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.InputStream;
28  import java.io.OutputStream;
29  import java.util.Arrays;
30  import java.util.List;
31  import javax.jcr.Binary;
32  import javax.jcr.Node;
33  import javax.jcr.NodeIterator;
34  import javax.jcr.Property;
35  import javax.jcr.PropertyIterator;
36  import javax.jcr.PropertyType;
37  import javax.jcr.RepositoryException;
38  import javax.jcr.Value;
39  import net.jcip.annotations.NotThreadSafe;
40  import org.modeshape.common.util.Base64;
41  import org.modeshape.common.xml.XmlCharacters;
42  import org.modeshape.graph.property.Name;
43  import org.modeshape.graph.property.ValueFactory;
44  import org.xml.sax.ContentHandler;
45  import org.xml.sax.SAXException;
46  import org.xml.sax.helpers.AttributesImpl;
47  
48  /**
49   * Implementation of {@link AbstractJcrExporter} that implements the system view mapping described in section 7.2 of the JCR 2.0
50   * specification.
51   * 
52   * @see JcrSession#exportSystemView(String, ContentHandler, boolean, boolean)
53   * @see JcrSession#exportSystemView(String, OutputStream, boolean, boolean)
54   */
55  @NotThreadSafe
56  class JcrSystemViewExporter extends AbstractJcrExporter {
57  
58      /**
59       * Buffer size for reading Base64-encoded binary streams for export.
60       */
61      private static final int BASE_64_BUFFER_SIZE = 1024;
62  
63      /**
64       * The list of the special JCR properties that must be exported first for each node. These properties must be exported in list
65       * order if they are present on the node as per section 6.4.1 rule 11.
66       */
67      private static final List<Name> SPECIAL_PROPERTY_NAMES = Arrays.asList(new Name[] {JcrLexicon.PRIMARY_TYPE,
68          JcrLexicon.MIXIN_TYPES, JcrLexicon.UUID});
69  
70      JcrSystemViewExporter( JcrSession session ) {
71          super(session, Arrays.asList(new String[] {"xml"}));
72      }
73  
74      /**
75       * Exports <code>node</code> (or the subtree rooted at <code>node</code>) into an XML document by invoking SAX events on
76       * <code>contentHandler</code>.
77       * 
78       * @param node the node which should be exported. If <code>noRecursion</code> was set to <code>false</code> in the
79       *        constructor, the entire subtree rooted at <code>node</code> will be exported.
80       * @param contentHandler the SAX content handler for which SAX events will be invoked as the XML document is created.
81       * @param skipBinary if <code>true</code>, indicates that binary properties should not be exported
82       * @param noRecurse if<code>true</code>, indicates that only the given node should be exported, otherwise a recursive export
83       *        and not any of its child nodes.
84       * @throws SAXException if an exception occurs during generation of the XML document
85       * @throws RepositoryException if an exception occurs accessing the content repository
86       */
87      @Override
88      public void exportNode( Node node,
89                              ContentHandler contentHandler,
90                              boolean skipBinary,
91                              boolean noRecurse ) throws RepositoryException, SAXException {
92          exportNode(node, contentHandler, skipBinary, noRecurse, node.getDepth() == 0);
93      }
94  
95      /**
96       * Exports <code>node</code> (or the subtree rooted at <code>node</code>) into an XML document by invoking SAX events on
97       * <code>contentHandler</code>.
98       * 
99       * @param node the node which should be exported. If <code>noRecursion</code> was set to <code>false</code> in the
100      *        constructor, the entire subtree rooted at <code>node</code> will be exported.
101      * @param contentHandler the SAX content handler for which SAX events will be invoked as the XML document is created.
102      * @param skipBinary if <code>true</code>, indicates that binary properties should not be exported
103      * @param noRecurse if<code>true</code>, indicates that only the given node should be exported, otherwise a recursive export
104      *        and not any of its child nodes.
105      * @param isRoot true if the supplied node is the root node (supplied as an efficiency)
106      * @throws SAXException if an exception occurs during generation of the XML document
107      * @throws RepositoryException if an exception occurs accessing the content repository
108      */
109     protected void exportNode( Node node,
110                                ContentHandler contentHandler,
111                                boolean skipBinary,
112                                boolean noRecurse,
113                                boolean isRoot ) throws RepositoryException, SAXException {
114 
115         // start the sv:node element for this JCR node
116         AttributesImpl atts = new AttributesImpl();
117         String nodeName = node.getName();
118         if (isRoot && node.getDepth() == 0) {
119             // This is the root node ...
120             nodeName = "jcr:root";
121         }
122         atts.addAttribute(JcrSvLexicon.NAME.getNamespaceUri(),
123                           JcrSvLexicon.NAME.getLocalName(),
124                           getPrefixedName(JcrSvLexicon.NAME),
125                           PropertyType.nameFromValue(PropertyType.STRING),
126                           nodeName);
127 
128         startElement(contentHandler, JcrSvLexicon.NODE, atts);
129 
130         if (node instanceof JcrSharedNode) {
131             // This is a shared node, and per Section 14.7 of the JCR 2.0 specification, they have to be written out
132             // in a special way ...
133 
134             // jcr:primaryType = nt:share ...
135             emitProperty(JcrLexicon.PRIMARY_TYPE, PropertyType.NAME, JcrNtLexicon.SHARE, contentHandler, skipBinary);
136 
137             // jcr:uuid = UUID of shared node ...
138             emitProperty(JcrLexicon.UUID, PropertyType.STRING, node.getIdentifier(), contentHandler, skipBinary);
139         } else {
140 
141             // Output any special properties first (see Javadoc for SPECIAL_PROPERTY_NAMES for more context)
142             for (Name specialPropertyName : SPECIAL_PROPERTY_NAMES) {
143                 Property specialProperty = ((AbstractJcrNode)node).getProperty(specialPropertyName);
144 
145                 if (specialProperty != null) {
146                     emitProperty(specialProperty, contentHandler, skipBinary);
147                 }
148             }
149 
150             PropertyIterator properties = node.getProperties();
151             while (properties.hasNext()) {
152                 exportProperty(properties.nextProperty(), contentHandler, skipBinary);
153             }
154 
155             if (!noRecurse) {
156                 NodeIterator nodes = node.getNodes();
157                 while (nodes.hasNext()) {
158                     exportNode(nodes.nextNode(), contentHandler, skipBinary, noRecurse, false);
159                 }
160             }
161         }
162 
163         endElement(contentHandler, JcrSvLexicon.NODE);
164     }
165 
166     /**
167      * @param property
168      * @param contentHandler the SAX content handler for which SAX events will be invoked as the XML document is created.
169      * @param skipBinary if <code>true</code>, indicates that binary properties should not be exported
170      * @throws SAXException if an exception occurs during generation of the XML document
171      * @throws RepositoryException if an exception occurs accessing the content repository
172      */
173     private void exportProperty( Property property,
174                                  ContentHandler contentHandler,
175                                  boolean skipBinary ) throws RepositoryException, SAXException {
176         assert property instanceof AbstractJcrProperty : "Illegal attempt to use " + getClass().getName()
177                                                          + " on non-ModeShape property";
178 
179         AbstractJcrProperty prop = (AbstractJcrProperty)property;
180 
181         Name propertyName = prop.name();
182         if (SPECIAL_PROPERTY_NAMES.contains(propertyName)) {
183             return;
184         }
185 
186         emitProperty(property, contentHandler, skipBinary);
187     }
188 
189     /**
190      * Fires the appropriate SAX events on the content handler to build the XML elements for the property.
191      * 
192      * @param property the property to be exported
193      * @param contentHandler the SAX content handler for which SAX events will be invoked as the XML document is created.
194      * @param skipBinary if <code>true</code>, indicates that binary properties should not be exported
195      * @throws SAXException if an exception occurs during generation of the XML document
196      * @throws RepositoryException if an exception occurs accessing the content repository
197      */
198     private void emitProperty( Property property,
199                                ContentHandler contentHandler,
200                                boolean skipBinary ) throws RepositoryException, SAXException {
201         assert property instanceof AbstractJcrProperty : "Illegal attempt to use " + getClass().getName()
202                                                          + " on non-ModeShape property";
203 
204         AbstractJcrProperty prop = (AbstractJcrProperty)property;
205 
206         // first set the property sv:name attribute
207         AttributesImpl propAtts = new AttributesImpl();
208         propAtts.addAttribute(JcrSvLexicon.NAME.getNamespaceUri(),
209                               JcrSvLexicon.NAME.getLocalName(),
210                               getPrefixedName(JcrSvLexicon.NAME),
211                               PropertyType.nameFromValue(PropertyType.STRING),
212                               prop.getName());
213 
214         // and it's sv:type attribute
215         propAtts.addAttribute(JcrSvLexicon.TYPE.getNamespaceUri(),
216                               JcrSvLexicon.TYPE.getLocalName(),
217                               getPrefixedName(JcrSvLexicon.TYPE),
218                               PropertyType.nameFromValue(PropertyType.STRING),
219                               PropertyType.nameFromValue(prop.getType()));
220 
221         // output the sv:property element
222         startElement(contentHandler, JcrSvLexicon.PROPERTY, propAtts);
223 
224         // then output a sv:value element for each of its values
225         if (prop instanceof JcrMultiValueProperty) {
226             Value[] values = prop.getValues();
227             for (int i = 0; i < values.length; i++) {
228 
229                 emitValue(values[i], contentHandler, property.getType(), skipBinary);
230             }
231         } else {
232             emitValue(property.getValue(), contentHandler, property.getType(), skipBinary);
233         }
234 
235         // end the sv:property element
236         endElement(contentHandler, JcrSvLexicon.PROPERTY);
237     }
238 
239     /**
240      * Fires the appropriate SAX events on the content handler to build the XML elements for the value.
241      * 
242      * @param value the value to be exported
243      * @param contentHandler the SAX content handler for which SAX events will be invoked as the XML document is created.
244      * @param propertyType the {@link PropertyType} for the given value
245      * @param skipBinary if <code>true</code>, indicates that binary properties should not be exported
246      * @throws SAXException if an exception occurs during generation of the XML document
247      * @throws RepositoryException if an exception occurs accessing the content repository
248      */
249     private void emitValue( Value value,
250                             ContentHandler contentHandler,
251                             int propertyType,
252                             boolean skipBinary ) throws RepositoryException, SAXException {
253 
254         if (PropertyType.BINARY == propertyType) {
255             startElement(contentHandler, JcrSvLexicon.VALUE, null);
256 
257             // Per section 6.5 of the 1.0.1 spec, we need to emit one empty-value tag for each value if the property is
258             // multi-valued and skipBinary is true
259             if (!skipBinary) {
260                 byte[] bytes = new byte[BASE_64_BUFFER_SIZE];
261                 int len;
262 
263                 Binary binary = value.getBinary();
264                 try {
265                     InputStream stream = new Base64.InputStream(binary.getStream(), Base64.ENCODE);
266                     while (-1 != (len = stream.read(bytes))) {
267                         contentHandler.characters(new String(bytes, 0, len).toCharArray(), 0, len);
268                     }
269                 } catch (IOException ioe) {
270                     throw new RepositoryException(ioe);
271                 } finally {
272                     binary.dispose();
273                 }
274             }
275             endElement(contentHandler, JcrSvLexicon.VALUE);
276         } else {
277             emitValue(value.getString(), contentHandler);
278         }
279     }
280 
281     private void emitValue( String value,
282                             ContentHandler contentHandler ) throws RepositoryException, SAXException {
283 
284         // Per Section 7.2 Rule 11a of the JCR 2.0 spec, need to check invalid XML characters
285 
286         char[] chars = value.toCharArray();
287 
288         boolean allCharsAreValidXml = true;
289         for (int i = 0; i < chars.length; i++) {
290             if (!XmlCharacters.isValid(chars[i])) {
291                 allCharsAreValidXml = false;
292                 break;
293             }
294         }
295 
296         if (allCharsAreValidXml) {
297 
298             startElement(contentHandler, JcrSvLexicon.VALUE, null);
299             contentHandler.characters(chars, 0, chars.length);
300             endElement(contentHandler, JcrSvLexicon.VALUE);
301         } else {
302             AttributesImpl valueAtts = new AttributesImpl();
303             valueAtts.addAttribute("xsi", "type", "xsi:type", "STRING", "xsd:base64Binary");
304 
305             startElement(contentHandler, JcrSvLexicon.VALUE, valueAtts);
306             try {
307                 chars = Base64.encodeBytes(value.getBytes("UTF-8")).toCharArray();
308             } catch (IOException ioe) {
309                 throw new RepositoryException(ioe);
310             }
311             contentHandler.characters(chars, 0, chars.length);
312             endElement(contentHandler, JcrSvLexicon.VALUE);
313         }
314     }
315 
316     /**
317      * Fires the appropriate SAX events on the content handler to build the XML elements for the property.
318      * 
319      * @param propertyName the name of the property to be exported
320      * @param propertyType the type of the property to be exported
321      * @param value the value of the single-valued property to be exported
322      * @param contentHandler the SAX content handler for which SAX events will be invoked as the XML document is created.
323      * @param skipBinary if <code>true</code>, indicates that binary properties should not be exported
324      * @throws SAXException if an exception occurs during generation of the XML document
325      * @throws RepositoryException if an exception occurs accessing the content repository
326      */
327     private void emitProperty( Name propertyName,
328                                int propertyType,
329                                Object value,
330                                ContentHandler contentHandler,
331                                boolean skipBinary ) throws RepositoryException, SAXException {
332         ValueFactory<String> strings = session.getExecutionContext().getValueFactories().getStringFactory();
333 
334         // first set the property sv:name attribute
335         AttributesImpl propAtts = new AttributesImpl();
336         propAtts.addAttribute(JcrSvLexicon.NAME.getNamespaceUri(),
337                               JcrSvLexicon.NAME.getLocalName(),
338                               getPrefixedName(JcrSvLexicon.NAME),
339                               PropertyType.nameFromValue(PropertyType.STRING),
340                               strings.create(propertyName));
341 
342         // and it's sv:type attribute
343         propAtts.addAttribute(JcrSvLexicon.TYPE.getNamespaceUri(),
344                               JcrSvLexicon.TYPE.getLocalName(),
345                               getPrefixedName(JcrSvLexicon.TYPE),
346                               PropertyType.nameFromValue(PropertyType.STRING),
347                               PropertyType.nameFromValue(propertyType));
348 
349         // output the sv:property element
350         startElement(contentHandler, JcrSvLexicon.PROPERTY, propAtts);
351 
352         // then output a sv:value element for each of its values
353         emitValue(strings.create(value), contentHandler);
354 
355         // end the sv:property element
356         endElement(contentHandler, JcrSvLexicon.PROPERTY);
357     }
358 }