1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
66
67
68
69
70
71
72
73
74 @NotThreadSafe
75 class JcrContentHandler extends DefaultHandler {
76
77
78
79
80
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
203
204
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
216
217
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
233
234
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
246
247
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
272
273
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
284 String existingUri = namespaces.getNamespaceForPrefix(prefix);
285
286 if (existingUri != null) {
287 if (existingUri.equals(uri)) {
288
289 return;
290 }
291 throw new RepositoryException("Prefix " + prefix + " is already permanently mapped");
292 }
293
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
308
309 EnclosingSAXException( Exception e ) {
310 super(e);
311
312 }
313
314 }
315
316
317
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
350
351
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
373
374
375 protected static final Set<Name> PROPERTIES_TO_SKIP_ON_IMPORT = Collections.unmodifiableSet(JcrLexicon.LOCK_IS_DEEP,
376 JcrLexicon.LOCK_OWNER);
377
378
379
380
381
382
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
408
409
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
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;
444
445
446 node.editor().setProperty(name, (JcrValue)valueFor(value, propertyType));
447 } else {
448
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
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
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
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
516 }
517
518 }
519
520
521 AbstractJcrNode existingNode = parent.getNode(nodeName);
522 boolean nodeAlreadyExists = existingNode != null && existingNode.getDefinition().isAutoCreated();
523
524
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
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
540 child = parent.editor().createChild(nodeName, uuid, primaryTypeName);
541 } else {
542 child = existingNode;
543 }
544
545
546 SessionCache.NodeEditor newNodeEditor = child.editor();
547
548
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
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
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
608
609
610
611 @Override
612 public AbstractJcrNode node() {
613 return node;
614 }
615
616
617
618
619
620
621 @Override
622 public NodeHandler parentHandler() {
623 return parentHandler;
624 }
625
626
627
628
629
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
647
648
649
650 @Override
651 public void addPropertyValue( Name propertyName,
652 String value,
653 int propertyType,
654 TextDecoder decoder ) {
655
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
668
669
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
693
694
695
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
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
739
740
741
742
743 @Override
744 public void startElement( String uri,
745 String localName,
746 String name,
747 Attributes atts ) throws SAXException {
748
749 currentPropertyValue = new StringBuilder();
750
751 if ("node".equals(localName)) {
752
753 current.finish();
754
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
767
768
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();
783 current = current.parentHandler();
784 } else if ("value".equals(localName)) {
785
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
805
806 DocumentViewContentHandler( AbstractJcrNode currentNode ) {
807 super();
808 this.current = new ExistingNodeHandler(currentNode, null);
809 this.nodeHandlerFactory = new StandardNodeHandlerFactory();
810 }
811
812
813
814
815
816
817
818 @Override
819 public void startElement( String uri,
820 String localName,
821 String name,
822 Attributes atts ) throws SAXException {
823
824 String nodeName = DOCUMENT_VIEW_NAME_DECODER.decode(name);
825 current = nodeHandlerFactory.createFor(nameFor(nodeName), current, uuidBehavior);
826
827
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);
832 }
833
834
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
848
849
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
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);
863 current.finish();
864
865 current = current.parentHandler();
866 }
867 }
868 }