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                     Name primaryTypeName = nameFor(typeName);
530                     if (JcrNtLexicon.SHARE.equals(primaryTypeName) && uuid != null) {
531                         // Per Section 14.7 and 14.8 of the JCR 2.0 specification, shared nodes are imported in a special way ...
532                         child = parent.editor().createChild(nodeName, UUID.randomUUID(), ModeShapeLexicon.SHARE);
533                         SessionCache.NodeEditor newNodeEditor = child.editor();
534                         JcrValue uuidValue = (JcrValue)valueFor(uuid.toString(), PropertyType.STRING);
535                         newNodeEditor.setProperty(ModeShapeLexicon.SHARED_UUID, uuidValue, false);
536                         node = child;
537                         return;
538                     }
539                     // Otherwise, it's just a regular node...
540                     child = parent.editor().createChild(nodeName, uuid, primaryTypeName);
541                 } else {
542                     child = existingNode;
543                 }
544 
545                 // Set the properties on the new node ...
546                 SessionCache.NodeEditor newNodeEditor = child.editor();
547 
548                 // Set the mixin types first (before we set any properties that may require the mixins to be present) ...
549                 List<Value> mixinTypeValueList = properties.get(JcrLexicon.MIXIN_TYPES);
550                 if (mixinTypeValueList != null) {
551                     for (Value value : mixinTypeValueList) {
552                         JcrNodeType mixinType = nodeTypeFor(value.getString());
553                         newNodeEditor.addMixin(mixinType);
554                     }
555                 }
556 
557                 boolean skipProtected = false;
558                 for (Map.Entry<Name, List<Value>> entry : properties.entrySet()) {
559                     Name propertyName = entry.getKey();
560 
561                     // These are all handled earlier ...
562                     if (JcrLexicon.PRIMARY_TYPE.equals(propertyName)) {
563                         continue;
564                     }
565                     if (JcrLexicon.MIXIN_TYPES.equals(propertyName)) {
566                         continue;
567                     }
568                     if (JcrLexicon.UUID.equals(propertyName)) {
569                         continue;
570                     }
571 
572                     // Should we ignore this property?
573                     if (shouldNotImportProperty(propertyName)) {
574                         continue;
575                     }
576 
577                     List<Value> values = entry.getValue();
578 
579                     if (values.size() == 1) {
580                         newNodeEditor.setProperty(propertyName, (JcrValue)values.get(0), skipProtected);
581                     } else {
582                         newNodeEditor.setProperty(propertyName,
583                                                   values.toArray(new Value[values.size()]),
584                                                   PropertyType.UNDEFINED,
585                                                   skipProtected);
586                     }
587                 }
588 
589                 node = child;
590             } catch (RepositoryException re) {
591                 throw new EnclosingSAXException(re);
592             }
593         }
594     }
595 
596     protected class ExistingNodeHandler extends NodeHandler {
597         private final AbstractJcrNode node;
598         private final NodeHandler parentHandler;
599 
600         protected ExistingNodeHandler( AbstractJcrNode node,
601                                        NodeHandler parentHandler ) {
602             this.node = node;
603             this.parentHandler = parentHandler;
604         }
605 
606         /**
607          * {@inheritDoc}
608          * 
609          * @see org.modeshape.jcr.JcrContentHandler.NodeHandler#node()
610          */
611         @Override
612         public AbstractJcrNode node() {
613             return node;
614         }
615 
616         /**
617          * {@inheritDoc}
618          * 
619          * @see org.modeshape.jcr.JcrContentHandler.NodeHandler#parentHandler()
620          */
621         @Override
622         public NodeHandler parentHandler() {
623             return parentHandler;
624         }
625 
626         /**
627          * {@inheritDoc}
628          * 
629          * @see org.modeshape.jcr.JcrContentHandler.NodeHandler#addPropertyValue(Name, String, int, TextDecoder)
630          */
631         @Override
632         public void addPropertyValue( Name propertyName,
633                                       String value,
634                                       int propertyType,
635                                       TextDecoder decoder ) {
636             throw new UnsupportedOperationException();
637         }
638     }
639 
640     protected class JcrRootHandler extends ExistingNodeHandler {
641         protected JcrRootHandler( AbstractJcrNode root ) {
642             super(root, null);
643         }
644 
645         /**
646          * {@inheritDoc}
647          * 
648          * @see org.modeshape.jcr.JcrContentHandler.NodeHandler#addPropertyValue(Name, String, int, TextDecoder)
649          */
650         @Override
651         public void addPropertyValue( Name propertyName,
652                                       String value,
653                                       int propertyType,
654                                       TextDecoder decoder ) {
655             // do nothing ...
656         }
657     }
658 
659     protected class IgnoreBranchHandler extends NodeHandler {
660         private NodeHandler parentHandler;
661 
662         protected IgnoreBranchHandler( NodeHandler parentHandler ) {
663             this.parentHandler = parentHandler;
664         }
665 
666         /**
667          * {@inheritDoc}
668          * 
669          * @see org.modeshape.jcr.JcrContentHandler.NodeHandler#parentHandler()
670          */
671         @Override
672         public NodeHandler parentHandler() {
673             return parentHandler;
674         }
675     }
676 
677     protected class JcrSystemHandler extends IgnoreBranchHandler {
678 
679         protected JcrSystemHandler( NodeHandler parentHandler ) {
680             super(parentHandler);
681         }
682     }
683 
684     protected interface NodeHandlerFactory {
685         NodeHandler createFor( Name nodeName,
686                                NodeHandler parentHandler,
687                                int uuidBehavior ) throws SAXException;
688     }
689 
690     protected class StandardNodeHandlerFactory implements NodeHandlerFactory {
691         /**
692          * {@inheritDoc}
693          * 
694          * @see org.modeshape.jcr.JcrContentHandler.NodeHandlerFactory#createFor(org.modeshape.graph.property.Name,
695          *      org.modeshape.jcr.JcrContentHandler.NodeHandler,int)
696          */
697         @Override
698         public NodeHandler createFor( Name name,
699                                       NodeHandler parentHandler,
700                                       int uuidBehavior ) throws SAXException {
701             if (parentHandler instanceof IgnoreBranchHandler) {
702                 return new IgnoreBranchHandler(parentHandler);
703             }
704             if (JcrLexicon.ROOT.equals(name)) {
705                 try {
706                     JcrRootNode rootNode = cache().findJcrRootNode();
707                     return new JcrRootHandler(rootNode);
708                 } catch (RepositoryException re) {
709                     throw new EnclosingSAXException(re);
710                 }
711             }
712             if (JcrLexicon.SYSTEM.equals(name)) {
713                 // Always do this, regardless of where the "jcr:system" branch is located ...
714                 return new JcrSystemHandler(parentHandler);
715             }
716             return new BasicNodeHandler(name, parentHandler, uuidBehavior);
717         }
718     }
719 
720     private class SystemViewContentHandler extends DefaultHandler {
721         private final String svNameName;
722         private final String svTypeName;
723         private NodeHandler current;
724         private final NodeHandlerFactory nodeHandlerFactory;
725         private String currentPropertyName;
726         private int currentPropertyType;
727         private StringBuilder currentPropertyValue;
728 
729         SystemViewContentHandler( AbstractJcrNode parent ) {
730             super();
731             this.svNameName = JcrSvLexicon.NAME.getString(namespaces());
732             this.svTypeName = JcrSvLexicon.TYPE.getString(namespaces());
733             this.current = new ExistingNodeHandler(parent, null);
734             this.nodeHandlerFactory = new StandardNodeHandlerFactory();
735         }
736 
737         /**
738          * {@inheritDoc}
739          * 
740          * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String,
741          *      org.xml.sax.Attributes)
742          */
743         @Override
744         public void startElement( String uri,
745                                   String localName,
746                                   String name,
747                                   Attributes atts ) throws SAXException {
748             // Always create a new string buffer for the content value, because we're starting a new element ...
749             currentPropertyValue = new StringBuilder();
750 
751             if ("node".equals(localName)) {
752                 // Finish the parent handler ...
753                 current.finish();
754                 // Create a new handler for this element ...
755                 String nodeName = atts.getValue(SYSTEM_VIEW_NAME_DECODER.decode(svNameName));
756                 current = nodeHandlerFactory.createFor(nameFor(nodeName), current, uuidBehavior);
757             } else if ("property".equals(localName)) {
758                 currentPropertyName = atts.getValue(SYSTEM_VIEW_NAME_DECODER.decode(svNameName));
759                 currentPropertyType = PropertyType.valueFromName(atts.getValue(svTypeName));
760             } else if (!"value".equals(localName)) {
761                 throw new IllegalStateException("Unexpected element '" + name + "' in system view");
762             }
763         }
764 
765         /**
766          * {@inheritDoc}
767          * 
768          * @see org.xml.sax.ContentHandler#characters(char[], int, int)
769          */
770         @Override
771         public void characters( char[] ch,
772                                 int start,
773                                 int length ) {
774             currentPropertyValue.append(ch, start, length);
775         }
776 
777         @Override
778         public void endElement( String uri,
779                                 String localName,
780                                 String name ) throws SAXException {
781             if ("node".equals(localName)) {
782                 current.finish(); // make sure the node is created
783                 current = current.parentHandler();
784             } else if ("value".equals(localName)) {
785                 // Add the content for the current property ...
786                 String currentPropertyString = currentPropertyValue.toString();
787                 current.addPropertyValue(nameFor(currentPropertyName),
788                                          currentPropertyString,
789                                          currentPropertyType,
790                                          SYSTEM_VIEW_NAME_DECODER);
791             } else if ("property".equals(localName)) {
792             } else {
793                 throw new IllegalStateException("Unexpected element '" + name + "' in system view");
794             }
795             currentPropertyValue = new StringBuilder();
796         }
797     }
798 
799     private class DocumentViewContentHandler extends DefaultHandler {
800         private NodeHandler current;
801         private final NodeHandlerFactory nodeHandlerFactory;
802 
803         /**
804          * @param currentNode
805          */
806         DocumentViewContentHandler( AbstractJcrNode currentNode ) {
807             super();
808             this.current = new ExistingNodeHandler(currentNode, null);
809             this.nodeHandlerFactory = new StandardNodeHandlerFactory();
810         }
811 
812         /**
813          * {@inheritDoc}
814          * 
815          * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String,
816          *      org.xml.sax.Attributes)
817          */
818         @Override
819         public void startElement( String uri,
820                                   String localName,
821                                   String name,
822                                   Attributes atts ) throws SAXException {
823             // Create the new handler for the new node ...
824             String nodeName = DOCUMENT_VIEW_NAME_DECODER.decode(name);
825             current = nodeHandlerFactory.createFor(nameFor(nodeName), current, uuidBehavior);
826 
827             // Add the properties ...
828             for (int i = 0; i < atts.getLength(); i++) {
829                 String value = atts.getValue(i);
830                 String propertyName = DOCUMENT_VIEW_NAME_DECODER.decode(atts.getQName(i));
831                 current.addPropertyValue(nameFor(propertyName), value, PropertyType.STRING, null);// don't decode the value
832             }
833 
834             // Now create the node ...
835             current.finish();
836         }
837 
838         @Override
839         public void endElement( String uri,
840                                 String localName,
841                                 String name ) throws SAXException {
842             current.finish();
843             current = current.parentHandler();
844         }
845 
846         /**
847          * {@inheritDoc}
848          * 
849          * @see org.xml.sax.ContentHandler#characters(char[], int, int)
850          */
851         @Override
852         public void characters( char[] ch,
853                                 int start,
854                                 int length ) throws SAXException {
855             String value = new String(ch, start, length);
856             // Create a 'jcr:xmltext' child node with a single 'jcr:xmlcharacters' property ...
857             current = nodeHandlerFactory.createFor(JcrLexicon.XMLTEXT, current, uuidBehavior);
858             current.addPropertyValue(JcrLexicon.PRIMARY_TYPE,
859                                      stringFor(JcrNtLexicon.UNSTRUCTURED),
860                                      PropertyType.NAME,
861                                      DOCUMENT_VIEW_NAME_DECODER);
862             current.addPropertyValue(JcrLexicon.XMLCHARACTERS, value, PropertyType.STRING, null);// don't decode value
863             current.finish();
864             // Pop the stack ...
865             current = current.parentHandler();
866         }
867     }
868 }