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 child = parent.editor().createChild(nodeName, uuid, nameFor(typeName));
530 } else {
531 child = existingNode;
532 }
533
534
535 SessionCache.NodeEditor newNodeEditor = child.editor();
536
537
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
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
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
597
598
599
600 @Override
601 public AbstractJcrNode node() {
602 return node;
603 }
604
605
606
607
608
609
610 @Override
611 public NodeHandler parentHandler() {
612 return parentHandler;
613 }
614
615
616
617
618
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
636
637
638
639 @Override
640 public void addPropertyValue( Name propertyName,
641 String value,
642 int propertyType,
643 TextDecoder decoder ) {
644
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
657
658
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
682
683
684
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
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
728
729
730
731
732 @Override
733 public void startElement( String uri,
734 String localName,
735 String name,
736 Attributes atts ) throws SAXException {
737
738 currentPropertyValue = new StringBuilder();
739
740 if ("node".equals(localName)) {
741
742 current.finish();
743
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
756
757
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();
772 current = current.parentHandler();
773 } else if ("value".equals(localName)) {
774
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
794
795 DocumentViewContentHandler( AbstractJcrNode currentNode ) {
796 super();
797 this.current = new ExistingNodeHandler(currentNode, null);
798 this.nodeHandlerFactory = new StandardNodeHandlerFactory();
799 }
800
801
802
803
804
805
806
807 @Override
808 public void startElement( String uri,
809 String localName,
810 String name,
811 Attributes atts ) throws SAXException {
812
813 String nodeName = DOCUMENT_VIEW_NAME_DECODER.decode(name);
814 current = nodeHandlerFactory.createFor(nameFor(nodeName), current, uuidBehavior);
815
816
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);
821 }
822
823
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
837
838
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
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);
852 current.finish();
853
854 current = current.parentHandler();
855 }
856 }
857 }