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.ByteArrayInputStream;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.util.ArrayList;
30  import java.util.HashMap;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  import java.util.UUID;
35  import javax.jcr.ImportUUIDBehavior;
36  import javax.jcr.ItemExistsException;
37  import javax.jcr.ItemNotFoundException;
38  import javax.jcr.PathNotFoundException;
39  import javax.jcr.PropertyType;
40  import javax.jcr.RepositoryException;
41  import javax.jcr.Value;
42  import javax.jcr.ValueFactory;
43  import javax.jcr.ValueFormatException;
44  import javax.jcr.nodetype.ConstraintViolationException;
45  import javax.jcr.version.VersionException;
46  import net.jcip.annotations.NotThreadSafe;
47  import org.modeshape.common.SystemFailureException;
48  import org.modeshape.common.collection.Collections;
49  import org.modeshape.common.text.TextDecoder;
50  import org.modeshape.common.text.XmlNameEncoder;
51  import org.modeshape.common.util.Base64;
52  import org.modeshape.graph.ExecutionContext;
53  import org.modeshape.graph.Location;
54  import org.modeshape.graph.property.Name;
55  import org.modeshape.graph.property.NameFactory;
56  import org.modeshape.graph.property.NamespaceRegistry;
57  import org.modeshape.graph.property.Path;
58  import org.modeshape.graph.property.PathFactory;
59  import org.xml.sax.Attributes;
60  import org.xml.sax.ContentHandler;
61  import org.xml.sax.SAXException;
62  import org.xml.sax.helpers.DefaultHandler;
63  
64  /**
65   * Content handler that provides SAX-based event handling that maps incoming documents to the repository based on the
66   * functionality described in section 7.3 of the JCR 1.0.1 specification.
67   * <p>
68   * Each content handler is only intended to be used once and discarded. This class is <b>NOT</b> thread-safe.
69   * </p>
70   * 
71   * @see JcrSession#getImportContentHandler(String, int)
72   * @see JcrWorkspace#getImportContentHandler(String, int)
73   */
74  @NotThreadSafe
75  class JcrContentHandler extends DefaultHandler {
76  
77      /**
78       * Encoder to properly escape XML names.
79       * 
80       * @see XmlNameEncoder
81       */
82      protected static final TextDecoder SYSTEM_VIEW_NAME_DECODER = new XmlNameEncoder();
83  
84      protected static final TextDecoder DOCUMENT_VIEW_NAME_DECODER = new JcrDocumentViewExporter.JcrDocumentViewPropertyEncoder();
85  
86      private static final String ALT_XML_SCHEMA_NAMESPACE_PREFIX = "xsd";
87      private final NameFactory nameFactory;
88      private final PathFactory pathFactory;
89      private final org.modeshape.graph.property.ValueFactory<String> stringFactory;
90      private final NamespaceRegistry namespaces;
91      private final ValueFactory jcrValueFactory;
92      private final JcrNodeTypeManager nodeTypes;
93      private final javax.jcr.NamespaceRegistry jcrNamespaceRegistry;
94      private final SaveMode saveMode;
95      protected final int uuidBehavior;
96  
97      protected final String primaryTypeName;
98      protected final String mixinTypesName;
99      protected final String uuidName;
100 
101     private AbstractJcrNode currentNode;
102     private ContentHandler delegate;
103 
104     private SessionCache cache;
105 
106     enum SaveMode {
107         WORKSPACE,
108         SESSION
109     }
110 
111     JcrContentHandler( JcrSession session,
112                        Path parentPath,
113                        int uuidBehavior,
114                        SaveMode saveMode ) throws PathNotFoundException, RepositoryException {
115         assert session != null;
116         assert parentPath != null;
117         assert uuidBehavior == ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW
118                || uuidBehavior == ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING
119                || uuidBehavior == ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING
120                || uuidBehavior == ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW;
121 
122         ExecutionContext context = session.getExecutionContext();
123         this.namespaces = context.getNamespaceRegistry();
124         this.nameFactory = context.getValueFactories().getNameFactory();
125         this.pathFactory = context.getValueFactories().getPathFactory();
126         this.stringFactory = context.getValueFactories().getStringFactory();
127         this.uuidBehavior = uuidBehavior;
128 
129         this.saveMode = saveMode;
130         switch (this.saveMode) {
131             case SESSION:
132                 cache = session.cache();
133                 break;
134             case WORKSPACE:
135                 cache = new SessionCache(session);
136                 break;
137         }
138         assert cache != null;
139 
140         try {
141             this.currentNode = cache.findJcrNode(null, parentPath);
142         } catch (ItemNotFoundException e) {
143             throw new PathNotFoundException(e.getLocalizedMessage(), e);
144         }
145 
146         if (!currentNode.isCheckedOut()) {
147             throw new VersionException(JcrI18n.nodeIsCheckedIn.text(currentNode.getPath()));
148         }
149 
150         this.jcrValueFactory = session.getValueFactory();
151         this.nodeTypes = session.nodeTypeManager();
152         this.jcrNamespaceRegistry = session.workspace().getNamespaceRegistry();
153 
154         this.primaryTypeName = JcrLexicon.PRIMARY_TYPE.getString(this.namespaces);
155         this.mixinTypesName = JcrLexicon.MIXIN_TYPES.getString(this.namespaces);
156         this.uuidName = JcrLexicon.UUID.getString(this.namespaces);
157     }
158 
159     protected final NamespaceRegistry namespaces() {
160         return namespaces;
161     }
162 
163     protected final JcrNodeTypeManager nodeTypes() {
164         return nodeTypes;
165     }
166 
167     protected final JcrNodeType nodeTypeFor( String name ) {
168         return nodeTypes.getNodeType(nameFor(name));
169     }
170 
171     protected final String stringFor( Object name ) {
172         return stringFactory.create(name);
173     }
174 
175     protected final Name nameFor( String name ) {
176         return nameFactory.create(name);
177     }
178 
179     protected final Path pathFor( Name... names ) {
180         return pathFor(pathFactory.createRootPath(), names);
181     }
182 
183     protected final Path pathFor( Path parentPath,
184                                   Name... names ) {
185         return pathFactory.create(parentPath, names);
186     }
187 
188     protected final Value valueFor( String value,
189                                     int type ) throws ValueFormatException {
190         return jcrValueFactory.createValue(value, type);
191     }
192 
193     protected final Value valueFor( InputStream stream ) throws RepositoryException {
194         return jcrValueFactory.createValue(jcrValueFactory.createBinary(stream));
195     }
196 
197     protected final SessionCache cache() {
198         return cache;
199     }
200 
201     /**
202      * {@inheritDoc}
203      * 
204      * @see org.xml.sax.ContentHandler#characters(char[], int, int)
205      */
206     @Override
207     public void characters( char[] ch,
208                             int start,
209                             int length ) throws SAXException {
210         assert this.delegate != null;
211         delegate.characters(ch, start, length);
212     }
213 
214     /**
215      * {@inheritDoc}
216      * 
217      * @see org.xml.sax.helpers.DefaultHandler#endDocument()
218      */
219     @Override
220     public void endDocument() throws SAXException {
221         if (saveMode == SaveMode.WORKSPACE) {
222             try {
223                 cache.save();
224             } catch (RepositoryException e) {
225                 throw new EnclosingSAXException(e);
226             }
227         }
228         super.endDocument();
229     }
230 
231     /**
232      * {@inheritDoc}
233      * 
234      * @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
235      */
236     @Override
237     public void endElement( String uri,
238                             String localName,
239                             String name ) throws SAXException {
240         assert this.delegate != null;
241         delegate.endElement(uri, localName, name);
242     }
243 
244     /**
245      * {@inheritDoc}
246      * 
247      * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
248      */
249     @Override
250     public void startElement( String uri,
251                               String localName,
252                               String name,
253                               Attributes atts ) throws SAXException {
254         checkDelegate(uri);
255         assert this.delegate != null;
256 
257         delegate.startElement(uri, localName, name, atts);
258     }
259 
260     private void checkDelegate( String namespaceUri ) {
261         if (delegate != null) return;
262 
263         if (JcrSvLexicon.Namespace.URI.equals(namespaceUri)) {
264             this.delegate = new SystemViewContentHandler(this.currentNode);
265         } else {
266             this.delegate = new DocumentViewContentHandler(this.currentNode);
267         }
268     }
269 
270     /**
271      * {@inheritDoc}
272      * 
273      * @see org.xml.sax.ContentHandler#startPrefixMapping(java.lang.String, java.lang.String)
274      */
275     @Override
276     public void startPrefixMapping( String prefix,
277                                     String uri ) throws SAXException {
278         try {
279             if (ALT_XML_SCHEMA_NAMESPACE_PREFIX.equals(prefix) && uri.equals(JcrNamespaceRegistry.XML_SCHEMA_NAMESPACE_URI)) {
280                 prefix = JcrNamespaceRegistry.XML_SCHEMA_NAMESPACE_PREFIX;
281             }
282 
283             // Read from the workspace's ModeShape registry, as its semantics are more friendly
284             String existingUri = namespaces.getNamespaceForPrefix(prefix);
285 
286             if (existingUri != null) {
287                 if (existingUri.equals(uri)) {
288                     // prefix/uri mapping is already in registry
289                     return;
290                 }
291                 throw new RepositoryException("Prefix " + prefix + " is already permanently mapped");
292             }
293             // Register through the JCR workspace to ensure consistency
294             this.jcrNamespaceRegistry.registerNamespace(prefix, uri);
295         } catch (RepositoryException re) {
296             throw new EnclosingSAXException(re);
297         }
298     }
299 
300     class EnclosingSAXException extends SAXException {
301 
302         /**
303          */
304         private static final long serialVersionUID = -1044992767566435542L;
305 
306         /**
307          * @param e
308          */
309         EnclosingSAXException( Exception e ) {
310             super(e);
311 
312         }
313 
314     }
315 
316     // ----------------------------------------------------------------------------------------------------------------
317     // NodeHandler framework ...
318     // ----------------------------------------------------------------------------------------------------------------
319 
320     @SuppressWarnings( "unused" )
321     protected abstract class NodeHandler {
322         public void finish() throws SAXException {
323         }
324 
325         public AbstractJcrNode node() throws SAXException {
326             return null;
327         }
328 
329         public NodeHandler parentHandler() {
330             return null;
331         }
332 
333         public void addPropertyValue( Name name,
334                                       String value,
335                                       int propertyType,
336                                       TextDecoder decoder ) throws EnclosingSAXException {
337         }
338 
339         protected String name() {
340             try {
341                 Path path = node().path();
342                 return path.isRoot() ? "" : stringFor(path.getLastSegment());
343             } catch (Exception e) {
344                 throw new SystemFailureException(e);
345             }
346         }
347 
348         /**
349          * {@inheritDoc}
350          * 
351          * @see java.lang.Object#toString()
352          */
353         @Override
354         public String toString() {
355             NodeHandler parent = parentHandler();
356             if (parent != null) {
357                 return parent.toString() + "/" + name();
358             }
359             try {
360                 return node().getPath();
361             } catch (Throwable e) {
362                 try {
363                     return node().toString();
364                 } catch (SAXException e2) {
365                     throw new SystemFailureException(e2);
366                 }
367             }
368         }
369     }
370 
371     /**
372      * The set of properties that should be skipped on import. Currently, this list includes all properties of "mix:lockable",
373      * since upon import no node should be locked.
374      */
375     protected static final Set<Name> PROPERTIES_TO_SKIP_ON_IMPORT = Collections.unmodifiableSet(JcrLexicon.LOCK_IS_DEEP,
376                                                                                                 JcrLexicon.LOCK_OWNER);
377 
378     // JcrLexicon.VERSION_HISTORY,
379     // JcrLexicon.PREDECESSORS,
380     // JcrLexicon.MERGE_FAILED,
381     // JcrLexicon.BASE_VERSION,
382     // JcrLexicon.IS_CHECKED_OUT,
383     // );
384 
385     protected class BasicNodeHandler extends NodeHandler {
386         private final Map<Name, List<Value>> properties;
387         private final Name nodeName;
388         private NodeHandler parentHandler;
389         private AbstractJcrNode node;
390         private final int uuidBehavior;
391 
392         protected BasicNodeHandler( Name name,
393                                     NodeHandler parentHandler,
394                                     int uuidBehavior ) {
395             this.nodeName = name;
396             this.parentHandler = parentHandler;
397             this.properties = new HashMap<Name, List<Value>>();
398             this.uuidBehavior = uuidBehavior;
399         }
400 
401         @Override
402         public void finish() throws SAXException {
403             node();
404         }
405 
406         /**
407          * {@inheritDoc}
408          * 
409          * @see org.modeshape.jcr.JcrContentHandler.NodeHandler#name()
410          */
411         @Override
412         protected String name() {
413             return stringFor(nodeName);
414         }
415 
416         @Override
417         public AbstractJcrNode node() throws SAXException {
418             if (node == null) create();
419             assert node != null;
420             return node;
421         }
422 
423         @Override
424         public NodeHandler parentHandler() {
425             return parentHandler;
426         }
427 
428         protected boolean shouldNotImportProperty( Name propertyName ) {
429             return false;
430             // return PROPERTIES_TO_SKIP_ON_IMPORT.contains(propertyName);
431         }
432 
433         @Override
434         public void addPropertyValue( Name name,
435                                       String value,
436                                       int propertyType,
437                                       TextDecoder decoder ) throws EnclosingSAXException {
438             try {
439                 if (node != null) {
440                     if (JcrLexicon.PRIMARY_TYPE.equals(name)) return;
441                     if (JcrLexicon.MIXIN_TYPES.equals(name)) return;
442                     if (JcrLexicon.UUID.equals(name)) return;
443                     if (shouldNotImportProperty(name)) return; // ignore some properties
444 
445                     // The node was already created, so set the property using the editor ...
446                     node.editor().setProperty(name, (JcrValue)valueFor(value, propertyType));
447                 } else {
448                     // The node hasn't been created yet, so just enqueue the property value into the map ...
449                     List<Value> values = properties.get(name);
450                     if (values == null) {
451                         values = new ArrayList<Value>();
452                         properties.put(name, values);
453                     }
454                     if (propertyType == PropertyType.BINARY) {
455                         ByteArrayInputStream is = new ByteArrayInputStream(Base64.decode(value, Base64.URL_SAFE));
456                         values.add(valueFor(is));
457                     } else {
458                         if (decoder != null) value = decoder.decode(value);
459                         if (value != null && propertyType == PropertyType.STRING) {
460                             // Strings and binaries can be empty -- other data types cannot
461                             values.add(valueFor(value, propertyType));
462                         } else if (value != null && value.length() > 0) {
463                             values.add(valueFor(value, propertyType));
464                         }
465                     }
466                 }
467             } catch (IOException ioe) {
468                 throw new EnclosingSAXException(ioe);
469             } catch (RepositoryException re) {
470                 throw new EnclosingSAXException(re);
471             }
472         }
473 
474         protected void create() throws SAXException {
475             try {
476                 AbstractJcrNode parent = parentHandler.node();
477                 assert parent != null;
478 
479                 // Figure out the UUID for the node ...
480                 UUID uuid = null;
481                 List<Value> rawUuid = properties.get(JcrLexicon.UUID);
482                 if (rawUuid != null) {
483                     assert rawUuid.size() == 1;
484                     uuid = UUID.fromString(rawUuid.get(0).getString());
485 
486                     try {
487                         // Deal with any existing node ...
488                         AbstractJcrNode existingNodeWithUuid = cache().findJcrNode(Location.create(uuid));
489                         switch (uuidBehavior) {
490                             case ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING:
491                                 parent = existingNodeWithUuid.getParent();
492                                 existingNodeWithUuid.remove();
493                                 break;
494                             case ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW:
495                                 uuid = UUID.randomUUID();
496                                 break;
497                             case ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING:
498                                 if (existingNodeWithUuid.path().isAtOrAbove(parent.path())) {
499                                     throw new ConstraintViolationException(
500                                                                            JcrI18n.cannotRemoveParentNodeOfTarget.text(existingNodeWithUuid.getPath(),
501                                                                                                                        uuid,
502                                                                                                                        parent.getPath()));
503                                 }
504                                 existingNodeWithUuid.remove();
505                                 break;
506                             case ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW:
507                                 throw new ItemExistsException(
508                                                               JcrI18n.itemAlreadyExistsWithUuid.text(uuid,
509                                                                                                      cache().session()
510                                                                                                             .workspace()
511                                                                                                             .getName(),
512                                                                                                      existingNodeWithUuid.getPath()));
513                         }
514                     } catch (ItemNotFoundException e) {
515                         // there wasn't an existing item, so just continue
516                     }
517 
518                 }
519 
520                 // See if the node was already autocreated by the parent
521                 AbstractJcrNode existingNode = parent.getNode(nodeName);
522                 boolean nodeAlreadyExists = existingNode != null && existingNode.getDefinition().isAutoCreated();
523 
524                 // Create the new node ...
525                 AbstractJcrNode child;
526                 if (!nodeAlreadyExists) {
527                     List<Value> primaryTypeValueList = properties.get(JcrLexicon.PRIMARY_TYPE);
528                     String typeName = primaryTypeValueList != null ? primaryTypeValueList.get(0).getString() : null;
529                     child = parent.editor().createChild(nodeName, uuid, nameFor(typeName));
530                 } else {
531                     child = existingNode;
532                 }
533 
534                 // Set the properties on the new node ...
535                 SessionCache.NodeEditor newNodeEditor = child.editor();
536 
537                 // Set the mixin types first (before we set any properties that may require the mixins to be present) ...
538                 List<Value> mixinTypeValueList = properties.get(JcrLexicon.MIXIN_TYPES);
539                 if (mixinTypeValueList != null) {
540                     for (Value value : mixinTypeValueList) {
541                         JcrNodeType mixinType = nodeTypeFor(value.getString());
542                         newNodeEditor.addMixin(mixinType);
543                     }
544                 }
545 
546                 boolean skipProtected = false;
547                 for (Map.Entry<Name, List<Value>> entry : properties.entrySet()) {
548                     Name propertyName = entry.getKey();
549 
550                     // These are all handled earlier ...
551                     if (JcrLexicon.PRIMARY_TYPE.equals(propertyName)) {
552                         continue;
553                     }
554                     if (JcrLexicon.MIXIN_TYPES.equals(propertyName)) {
555                         continue;
556                     }
557                     if (JcrLexicon.UUID.equals(propertyName)) {
558                         continue;
559                     }
560 
561                     // Should we ignore this property?
562                     if (shouldNotImportProperty(propertyName)) {
563                         continue;
564                     }
565 
566                     List<Value> values = entry.getValue();
567 
568                     if (values.size() == 1) {
569                         newNodeEditor.setProperty(propertyName, (JcrValue)values.get(0), skipProtected);
570                     } else {
571                         newNodeEditor.setProperty(propertyName,
572                                                   values.toArray(new Value[values.size()]),
573                                                   PropertyType.UNDEFINED,
574                                                   skipProtected);
575                     }
576                 }
577 
578                 node = child;
579             } catch (RepositoryException re) {
580                 throw new EnclosingSAXException(re);
581             }
582         }
583     }
584 
585     protected class ExistingNodeHandler extends NodeHandler {
586         private final AbstractJcrNode node;
587         private final NodeHandler parentHandler;
588 
589         protected ExistingNodeHandler( AbstractJcrNode node,
590                                        NodeHandler parentHandler ) {
591             this.node = node;
592             this.parentHandler = parentHandler;
593         }
594 
595         /**
596          * {@inheritDoc}
597          * 
598          * @see org.modeshape.jcr.JcrContentHandler.NodeHandler#node()
599          */
600         @Override
601         public AbstractJcrNode node() {
602             return node;
603         }
604 
605         /**
606          * {@inheritDoc}
607          * 
608          * @see org.modeshape.jcr.JcrContentHandler.NodeHandler#parentHandler()
609          */
610         @Override
611         public NodeHandler parentHandler() {
612             return parentHandler;
613         }
614 
615         /**
616          * {@inheritDoc}
617          * 
618          * @see org.modeshape.jcr.JcrContentHandler.NodeHandler#addPropertyValue(Name, String, int, TextDecoder)
619          */
620         @Override
621         public void addPropertyValue( Name propertyName,
622                                       String value,
623                                       int propertyType,
624                                       TextDecoder decoder ) {
625             throw new UnsupportedOperationException();
626         }
627     }
628 
629     protected class JcrRootHandler extends ExistingNodeHandler {
630         protected JcrRootHandler( AbstractJcrNode root ) {
631             super(root, null);
632         }
633 
634         /**
635          * {@inheritDoc}
636          * 
637          * @see org.modeshape.jcr.JcrContentHandler.NodeHandler#addPropertyValue(Name, String, int, TextDecoder)
638          */
639         @Override
640         public void addPropertyValue( Name propertyName,
641                                       String value,
642                                       int propertyType,
643                                       TextDecoder decoder ) {
644             // do nothing ...
645         }
646     }
647 
648     protected class IgnoreBranchHandler extends NodeHandler {
649         private NodeHandler parentHandler;
650 
651         protected IgnoreBranchHandler( NodeHandler parentHandler ) {
652             this.parentHandler = parentHandler;
653         }
654 
655         /**
656          * {@inheritDoc}
657          * 
658          * @see org.modeshape.jcr.JcrContentHandler.NodeHandler#parentHandler()
659          */
660         @Override
661         public NodeHandler parentHandler() {
662             return parentHandler;
663         }
664     }
665 
666     protected class JcrSystemHandler extends IgnoreBranchHandler {
667 
668         protected JcrSystemHandler( NodeHandler parentHandler ) {
669             super(parentHandler);
670         }
671     }
672 
673     protected interface NodeHandlerFactory {
674         NodeHandler createFor( Name nodeName,
675                                NodeHandler parentHandler,
676                                int uuidBehavior ) throws SAXException;
677     }
678 
679     protected class StandardNodeHandlerFactory implements NodeHandlerFactory {
680         /**
681          * {@inheritDoc}
682          * 
683          * @see org.modeshape.jcr.JcrContentHandler.NodeHandlerFactory#createFor(org.modeshape.graph.property.Name,
684          *      org.modeshape.jcr.JcrContentHandler.NodeHandler,int)
685          */
686         @Override
687         public NodeHandler createFor( Name name,
688                                       NodeHandler parentHandler,
689                                       int uuidBehavior ) throws SAXException {
690             if (parentHandler instanceof IgnoreBranchHandler) {
691                 return new IgnoreBranchHandler(parentHandler);
692             }
693             if (JcrLexicon.ROOT.equals(name)) {
694                 try {
695                     JcrRootNode rootNode = cache().findJcrRootNode();
696                     return new JcrRootHandler(rootNode);
697                 } catch (RepositoryException re) {
698                     throw new EnclosingSAXException(re);
699                 }
700             }
701             if (JcrLexicon.SYSTEM.equals(name)) {
702                 // Always do this, regardless of where the "jcr:system" branch is located ...
703                 return new JcrSystemHandler(parentHandler);
704             }
705             return new BasicNodeHandler(name, parentHandler, uuidBehavior);
706         }
707     }
708 
709     private class SystemViewContentHandler extends DefaultHandler {
710         private final String svNameName;
711         private final String svTypeName;
712         private NodeHandler current;
713         private final NodeHandlerFactory nodeHandlerFactory;
714         private String currentPropertyName;
715         private int currentPropertyType;
716         private StringBuilder currentPropertyValue;
717 
718         SystemViewContentHandler( AbstractJcrNode parent ) {
719             super();
720             this.svNameName = JcrSvLexicon.NAME.getString(namespaces());
721             this.svTypeName = JcrSvLexicon.TYPE.getString(namespaces());
722             this.current = new ExistingNodeHandler(parent, null);
723             this.nodeHandlerFactory = new StandardNodeHandlerFactory();
724         }
725 
726         /**
727          * {@inheritDoc}
728          * 
729          * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String,
730          *      org.xml.sax.Attributes)
731          */
732         @Override
733         public void startElement( String uri,
734                                   String localName,
735                                   String name,
736                                   Attributes atts ) throws SAXException {
737             // Always create a new string buffer for the content value, because we're starting a new element ...
738             currentPropertyValue = new StringBuilder();
739 
740             if ("node".equals(localName)) {
741                 // Finish the parent handler ...
742                 current.finish();
743                 // Create a new handler for this element ...
744                 String nodeName = atts.getValue(SYSTEM_VIEW_NAME_DECODER.decode(svNameName));
745                 current = nodeHandlerFactory.createFor(nameFor(nodeName), current, uuidBehavior);
746             } else if ("property".equals(localName)) {
747                 currentPropertyName = atts.getValue(SYSTEM_VIEW_NAME_DECODER.decode(svNameName));
748                 currentPropertyType = PropertyType.valueFromName(atts.getValue(svTypeName));
749             } else if (!"value".equals(localName)) {
750                 throw new IllegalStateException("Unexpected element '" + name + "' in system view");
751             }
752         }
753 
754         /**
755          * {@inheritDoc}
756          * 
757          * @see org.xml.sax.ContentHandler#characters(char[], int, int)
758          */
759         @Override
760         public void characters( char[] ch,
761                                 int start,
762                                 int length ) {
763             currentPropertyValue.append(ch, start, length);
764         }
765 
766         @Override
767         public void endElement( String uri,
768                                 String localName,
769                                 String name ) throws SAXException {
770             if ("node".equals(localName)) {
771                 current.finish(); // make sure the node is created
772                 current = current.parentHandler();
773             } else if ("value".equals(localName)) {
774                 // Add the content for the current property ...
775                 String currentPropertyString = currentPropertyValue.toString();
776                 current.addPropertyValue(nameFor(currentPropertyName),
777                                          currentPropertyString,
778                                          currentPropertyType,
779                                          SYSTEM_VIEW_NAME_DECODER);
780             } else if ("property".equals(localName)) {
781             } else {
782                 throw new IllegalStateException("Unexpected element '" + name + "' in system view");
783             }
784             currentPropertyValue = new StringBuilder();
785         }
786     }
787 
788     private class DocumentViewContentHandler extends DefaultHandler {
789         private NodeHandler current;
790         private final NodeHandlerFactory nodeHandlerFactory;
791 
792         /**
793          * @param currentNode
794          */
795         DocumentViewContentHandler( AbstractJcrNode currentNode ) {
796             super();
797             this.current = new ExistingNodeHandler(currentNode, null);
798             this.nodeHandlerFactory = new StandardNodeHandlerFactory();
799         }
800 
801         /**
802          * {@inheritDoc}
803          * 
804          * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String,
805          *      org.xml.sax.Attributes)
806          */
807         @Override
808         public void startElement( String uri,
809                                   String localName,
810                                   String name,
811                                   Attributes atts ) throws SAXException {
812             // Create the new handler for the new node ...
813             String nodeName = DOCUMENT_VIEW_NAME_DECODER.decode(name);
814             current = nodeHandlerFactory.createFor(nameFor(nodeName), current, uuidBehavior);
815 
816             // Add the properties ...
817             for (int i = 0; i < atts.getLength(); i++) {
818                 String value = atts.getValue(i);
819                 String propertyName = DOCUMENT_VIEW_NAME_DECODER.decode(atts.getQName(i));
820                 current.addPropertyValue(nameFor(propertyName), value, PropertyType.STRING, null);// don't decode the value
821             }
822 
823             // Now create the node ...
824             current.finish();
825         }
826 
827         @Override
828         public void endElement( String uri,
829                                 String localName,
830                                 String name ) throws SAXException {
831             current.finish();
832             current = current.parentHandler();
833         }
834 
835         /**
836          * {@inheritDoc}
837          * 
838          * @see org.xml.sax.ContentHandler#characters(char[], int, int)
839          */
840         @Override
841         public void characters( char[] ch,
842                                 int start,
843                                 int length ) throws SAXException {
844             String value = new String(ch, start, length);
845             // Create a 'jcr:xmltext' child node with a single 'jcr:xmlcharacters' property ...
846             current = nodeHandlerFactory.createFor(JcrLexicon.XMLTEXT, current, uuidBehavior);
847             current.addPropertyValue(JcrLexicon.PRIMARY_TYPE,
848                                      stringFor(JcrNtLexicon.UNSTRUCTURED),
849                                      PropertyType.NAME,
850                                      DOCUMENT_VIEW_NAME_DECODER);
851             current.addPropertyValue(JcrLexicon.XMLCHARACTERS, value, PropertyType.STRING, null);// don't decode value
852             current.finish();
853             // Pop the stack ...
854             current = current.parentHandler();
855         }
856     }
857 }