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.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Calendar;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.LinkedHashMap;
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Set;
38 import java.util.UUID;
39 import javax.jcr.AccessDeniedException;
40 import javax.jcr.InvalidItemStateException;
41 import javax.jcr.ItemExistsException;
42 import javax.jcr.ItemNotFoundException;
43 import javax.jcr.MergeException;
44 import javax.jcr.NoSuchWorkspaceException;
45 import javax.jcr.NodeIterator;
46 import javax.jcr.PathNotFoundException;
47 import javax.jcr.Property;
48 import javax.jcr.PropertyIterator;
49 import javax.jcr.PropertyType;
50 import javax.jcr.RepositoryException;
51 import javax.jcr.UnsupportedRepositoryOperationException;
52 import javax.jcr.Value;
53 import javax.jcr.lock.LockException;
54 import javax.jcr.nodetype.ConstraintViolationException;
55 import javax.jcr.nodetype.NodeDefinition;
56 import javax.jcr.nodetype.PropertyDefinition;
57 import javax.jcr.version.OnParentVersionAction;
58 import javax.jcr.version.Version;
59 import javax.jcr.version.VersionException;
60 import javax.jcr.version.VersionHistory;
61 import javax.jcr.version.VersionIterator;
62 import javax.jcr.version.VersionManager;
63 import net.jcip.annotations.NotThreadSafe;
64 import org.modeshape.common.i18n.I18n;
65 import org.modeshape.common.text.Jsr283Encoder;
66 import org.modeshape.common.text.TextEncoder;
67 import org.modeshape.common.util.CheckArg;
68 import org.modeshape.common.util.Logger;
69 import org.modeshape.graph.ExecutionContext;
70 import org.modeshape.graph.Graph;
71 import org.modeshape.graph.Location;
72 import org.modeshape.graph.Graph.Batch;
73 import org.modeshape.graph.property.DateTime;
74 import org.modeshape.graph.property.DateTimeFactory;
75 import org.modeshape.graph.property.Name;
76 import org.modeshape.graph.property.Path;
77 import org.modeshape.graph.property.PropertyFactory;
78 import org.modeshape.graph.property.Reference;
79 import org.modeshape.graph.property.ValueFactories;
80 import org.modeshape.graph.property.ValueFactory;
81 import org.modeshape.graph.property.Path.Segment;
82 import org.modeshape.graph.session.GraphSession.Node;
83 import org.modeshape.graph.session.GraphSession.PropertyInfo;
84 import org.modeshape.jcr.SessionCache.JcrNodePayload;
85 import org.modeshape.jcr.SessionCache.JcrPropertyPayload;
86 import org.modeshape.jcr.SessionCache.NodeEditor;
87
88
89
90
91
92 final class JcrVersionManager implements VersionManager {
93
94 private static final Logger LOGGER = Logger.getLogger(JcrVersionManager.class);
95
96 private static final TextEncoder NODE_ENCODER = new Jsr283Encoder();
97
98 static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
99
100
101
102
103 static final Set<Name> IGNORED_PROP_NAMES_FOR_RESTORE = Collections.unmodifiableSet(new HashSet<Name>(
104 Arrays.asList(new Name[] {
105 JcrLexicon.FROZEN_PRIMARY_TYPE,
106 JcrLexicon.FROZEN_MIXIN_TYPES,
107 JcrLexicon.FROZEN_UUID,
108 JcrLexicon.PRIMARY_TYPE,
109 JcrLexicon.MIXIN_TYPES,
110 JcrLexicon.UUID})));
111
112 private final JcrSession session;
113
114 public JcrVersionManager( JcrSession session ) {
115 super();
116 this.session = session;
117 }
118
119 ExecutionContext context() {
120 return session.getExecutionContext();
121 }
122
123 private ValueFactories factories() {
124 return context().getValueFactories();
125 }
126
127 UUID uuid( Object ob ) {
128 return factories().getUuidFactory().create(ob);
129 }
130
131 Name name( String s ) {
132 return factories().getNameFactory().create(s);
133 }
134
135 Name name( Object ob ) {
136 return factories().getNameFactory().create(ob);
137 }
138
139 final Path path( Path root,
140 Name child ) {
141 return factories().getPathFactory().create(root, child);
142 }
143
144 Path path( Path root,
145 Path.Segment childSegment ) {
146 return factories().getPathFactory().create(root, childSegment);
147 }
148
149 private Path absolutePath( Name... absolutePathSegments ) {
150 return factories().getPathFactory().createAbsolutePath(absolutePathSegments);
151 }
152
153 DateTime dateTime( Calendar cal ) {
154 return factories().getDateFactory().create(cal);
155 }
156
157 private PropertyFactory propertyFactory() {
158 return context().getPropertyFactory();
159 }
160
161 SessionCache cache() {
162 return session.cache();
163 }
164
165 private JcrRepository repository() {
166 return session.repository();
167 }
168
169 JcrSession session() {
170 return session;
171 }
172
173 JcrWorkspace workspace() {
174 return session.workspace();
175 }
176
177
178
179
180
181
182
183
184
185 Path versionHistoryPathFor( UUID uuid ) {
186 return absolutePath(JcrLexicon.SYSTEM, JcrLexicon.VERSION_STORAGE, name(uuid.toString()));
187 }
188
189
190
191
192
193
194
195
196
197
198 JcrVersionHistoryNode getVersionHistory( AbstractJcrNode node ) throws RepositoryException {
199 session.checkLive();
200 checkVersionable(node);
201
202 Location historyLocation = Location.create(versionHistoryPathFor(node.uuid()));
203 try {
204 return (JcrVersionHistoryNode)cache().findJcrNode(historyLocation);
205 } catch (ItemNotFoundException infe) {
206 initializeVersionHistoryFor(node);
207
208
209 JcrVersionHistoryNode historyNode = (JcrVersionHistoryNode)cache().findJcrNode(historyLocation);
210
211 LOGGER.warn(JcrI18n.repairedVersionStorage, historyLocation);
212
213 return historyNode;
214 }
215 }
216
217
218
219
220
221
222
223
224
225 JcrNodeDefinition nodeDefinitionFor( Node<JcrNodePayload, JcrPropertyPayload> node ) throws RepositoryException {
226 NodeDefinitionId nodeDefnId = node.getPayload().getDefinitionId();
227 return session().nodeTypeManager().getNodeDefinition(nodeDefnId);
228 }
229
230
231
232
233
234
235
236
237
238 JcrVersionNode checkin( AbstractJcrNode node ) throws RepositoryException {
239
240 session.checkLive();
241
242 checkVersionable(node);
243
244 if (node.isNew() || node.isModified()) {
245 throw new InvalidItemStateException(JcrI18n.noPendingChangesAllowed.text());
246 }
247
248
249 if (node.isLocked() && !node.holdsLock()) {
250 throw new LockException(JcrI18n.lockTokenNotHeld.text(node.getPath()));
251 }
252
253 if (node.getProperty(JcrLexicon.MERGE_FAILED) != null) {
254 throw new VersionException(JcrI18n.pendingMergeConflicts.text(node.getPath()));
255 }
256
257 Property isCheckedOut = node.getProperty(JcrLexicon.IS_CHECKED_OUT);
258
259 if (!isCheckedOut.getBoolean()) {
260 return node.getBaseVersion();
261 }
262
263 Name primaryTypeName = node.getPrimaryTypeName();
264 List<Name> mixinTypeNames = node.getMixinTypeNames();
265
266 UUID jcrUuid = node.uuid();
267 UUID versionUuid = UUID.randomUUID();
268
269 AbstractJcrNode historyNode = getVersionHistory(node);
270 Path historyPath = historyNode.path();
271
272 Graph systemGraph = repository().createSystemGraph(context());
273 Graph.Batch systemBatch = systemGraph.batch();
274 DateTime now = context().getValueFactories().getDateFactory().create();
275
276 Path versionPath = path(historyPath, name(NODE_ENCODER.encode(now.getString())));
277 AbstractJcrProperty predecessorsProp = node.getProperty(JcrLexicon.PREDECESSORS);
278
279 systemBatch.create(versionPath)
280 .with(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.VERSION)
281 .and(JcrLexicon.CREATED, now)
282 .and(JcrLexicon.UUID, versionUuid)
283 .and(predecessorsProp.property())
284 .and();
285 Path frozenVersionPath = path(versionPath, JcrLexicon.FROZEN_NODE);
286 systemBatch.create(frozenVersionPath)
287 .with(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.FROZEN_NODE)
288 .and(JcrLexicon.FROZEN_UUID, jcrUuid)
289 .and(JcrLexicon.FROZEN_PRIMARY_TYPE, primaryTypeName)
290 .and(JcrLexicon.FROZEN_MIXIN_TYPES, mixinTypeNames)
291 .and(versionedPropertiesFor(node))
292 .and();
293 int onParentVersion = node.getDefinition().getOnParentVersion();
294 for (NodeIterator childNodes = node.getNodes(); childNodes.hasNext();) {
295 AbstractJcrNode childNode = (AbstractJcrNode)childNodes.nextNode();
296 versionNodeAt(childNode, frozenVersionPath, systemBatch, onParentVersion);
297 }
298
299 PropertyFactory propFactory = propertyFactory();
300
301 for (Object ob : predecessorsProp.property()) {
302 UUID predUuid = uuid(ob);
303
304 org.modeshape.graph.property.Property successorsProp = systemGraph.getNodeAt(predUuid)
305 .getProperty(JcrLexicon.SUCCESSORS);
306
307 List<Object> newSuccessors = new LinkedList<Object>();
308 boolean alreadySuccessor = false;
309 if (successorsProp != null) {
310 for (Object successor : successorsProp) {
311 newSuccessors.add(successor);
312 if (uuid(successor).equals(predUuid)) alreadySuccessor = true;
313 }
314 }
315
316 if (!alreadySuccessor) {
317 newSuccessors.add(versionUuid);
318
319 org.modeshape.graph.property.Property newSuccessorsProp = propFactory.create(JcrLexicon.SUCCESSORS,
320 newSuccessors.toArray());
321 systemBatch.set(newSuccessorsProp).on(predUuid).and();
322 }
323 }
324
325 systemBatch.execute();
326 historyNode.refresh(false);
327
328 AbstractJcrNode newVersion = cache().findJcrNode(Location.create(versionUuid));
329
330 NodeEditor editor = node.editor();
331 editor.setProperty(JcrLexicon.PREDECESSORS,
332 node.valuesFrom(PropertyType.REFERENCE, EMPTY_OBJECT_ARRAY),
333 PropertyType.REFERENCE,
334 false);
335 editor.setProperty(JcrLexicon.BASE_VERSION, node.valueFrom(newVersion), false, false);
336 editor.setProperty(JcrLexicon.IS_CHECKED_OUT, node.valueFrom(PropertyType.BOOLEAN, false), false, false);
337 node.save();
338
339 return (JcrVersionNode)newVersion;
340 }
341
342
343
344
345
346
347
348
349
350
351
352 @SuppressWarnings( "fallthrough" )
353 private void versionNodeAt( AbstractJcrNode node,
354 Path verisonedParentPath,
355 Graph.Batch batch,
356 int onParentVersionAction ) throws RepositoryException {
357 Path childPath = path(verisonedParentPath, node.path().getLastSegment());
358
359 Name primaryTypeName = node.getPrimaryTypeName();
360 List<Name> mixinTypeNames = node.getMixinTypeNames();
361 UUID uuid = UUID.randomUUID();
362 if (node.isReferenceable()) uuid = node.uuid();
363
364 switch (onParentVersionAction) {
365 case OnParentVersionAction.ABORT:
366 throw new VersionException(JcrI18n.cannotCheckinNodeWithAbortChildNode.text(node.getName(), node.getParent()
367 .getName()));
368 case OnParentVersionAction.VERSION:
369 if (node.isNodeType(JcrMixLexicon.VERSIONABLE)) {
370 JcrVersionHistoryNode history = node.getVersionHistory();
371 UUID historyUuid = history.uuid();
372 batch.create(childPath)
373 .with(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.VERSIONED_CHILD)
374 .with(JcrLexicon.CHILD_VERSION_HISTORY, historyUuid)
375 .and();
376
377 break;
378 }
379
380
381 case OnParentVersionAction.COPY:
382 batch.create(childPath)
383 .with(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.FROZEN_NODE)
384 .and(JcrLexicon.FROZEN_PRIMARY_TYPE, primaryTypeName)
385 .and(JcrLexicon.FROZEN_MIXIN_TYPES, mixinTypeNames)
386 .and(JcrLexicon.FROZEN_UUID, uuid)
387 .and(versionedPropertiesFor(node))
388 .and();
389 break;
390 case OnParentVersionAction.INITIALIZE:
391 case OnParentVersionAction.COMPUTE:
392 case OnParentVersionAction.IGNORE:
393
394 return;
395 default:
396 throw new IllegalStateException("Unexpected value: " + onParentVersionAction);
397 }
398
399 for (NodeIterator childNodes = node.getNodes(); childNodes.hasNext();) {
400 AbstractJcrNode childNode = (AbstractJcrNode)childNodes.nextNode();
401 versionNodeAt(childNode, childPath, batch, onParentVersionAction);
402 }
403
404 }
405
406
407
408
409
410
411 private Collection<org.modeshape.graph.property.Property> versionedPropertiesFor( AbstractJcrNode node )
412 throws RepositoryException {
413
414 Collection<org.modeshape.graph.property.Property> props = new LinkedList<org.modeshape.graph.property.Property>();
415
416
417 AbstractJcrProperty multiValuedProperties = node.getProperty(ModeShapeIntLexicon.MULTI_VALUED_PROPERTIES);
418 if (multiValuedProperties != null) props.add(multiValuedProperties.property());
419
420 for (PropertyIterator iter = node.getProperties(); iter.hasNext();) {
421 AbstractJcrProperty property = (AbstractJcrProperty)iter.nextProperty();
422
423 org.modeshape.graph.property.Property prop = property.property();
424 PropertyDefinitionId propDefnId = property.propertyInfo().getPayload().getPropertyDefinitionId();
425 JcrPropertyDefinition propDefn = cache().nodeTypes().getPropertyDefinition(propDefnId);
426
427 switch (propDefn.getOnParentVersion()) {
428 case OnParentVersionAction.ABORT:
429 I18n msg = JcrI18n.cannotCheckinNodeWithAbortProperty;
430 throw new VersionException(msg.text(property.getName(), node.getName()));
431 case OnParentVersionAction.COPY:
432 case OnParentVersionAction.VERSION:
433 props.add(prop);
434 break;
435 case OnParentVersionAction.INITIALIZE:
436 case OnParentVersionAction.COMPUTE:
437 case OnParentVersionAction.IGNORE:
438
439 }
440 }
441
442 return props;
443 }
444
445
446
447
448
449
450
451
452
453 void checkout( AbstractJcrNode node ) throws LockException, RepositoryException {
454 session.checkLive();
455 checkVersionable(node);
456
457
458 if (node.isLocked() && !node.holdsLock()) {
459 throw new LockException(JcrI18n.lockTokenNotHeld.text(node.getPath()));
460 }
461
462
463
464
465
466 if (!node.hasProperty(JcrLexicon.BASE_VERSION)) {
467 return;
468 }
469
470
471 if (node.getProperty(JcrLexicon.IS_CHECKED_OUT).getBoolean()) {
472 return;
473 }
474
475 PropertyFactory propFactory = propertyFactory();
476
477 PropertyInfo<JcrPropertyPayload> mvProp = node.nodeInfo().getProperty(ModeShapeIntLexicon.MULTI_VALUED_PROPERTIES);
478 org.modeshape.graph.property.Property multiValuedProps = mvProp != null ? mvProp.getProperty() : null;
479
480 if (multiValuedProps == null) {
481 multiValuedProps = propFactory.create(ModeShapeIntLexicon.MULTI_VALUED_PROPERTIES, JcrLexicon.PREDECESSORS);
482 } else if (!Arrays.<Object>asList(multiValuedProps.getValues()).contains(JcrLexicon.PREDECESSORS)) {
483 List<Object> values = new LinkedList<Object>();
484
485 for (Object value : multiValuedProps) {
486 values.add(value);
487 }
488
489 values.add(JcrLexicon.PREDECESSORS);
490 multiValuedProps = propFactory.create(ModeShapeIntLexicon.MULTI_VALUED_PROPERTIES, values);
491 }
492
493 ValueFactory<Reference> refFactory = context().getValueFactories().getReferenceFactory();
494 Object[] oldPreds = EMPTY_OBJECT_ARRAY;
495
496 AbstractJcrProperty oldPredsProperty = node.getBaseVersion().getProperty(JcrLexicon.PREDECESSORS);
497 if (oldPredsProperty != null) {
498 oldPreds = oldPredsProperty.property().getValuesAsArray();
499 }
500
501 Object[] newPreds = new Object[oldPreds.length + 1];
502 newPreds[0] = refFactory.create(node.getBaseVersion().uuid());
503 System.arraycopy(oldPreds, 0, newPreds, 1, oldPreds.length);
504
505 org.modeshape.graph.property.Property isCheckedOut = propFactory.create(JcrLexicon.IS_CHECKED_OUT, true);
506 org.modeshape.graph.property.Property predecessors = propFactory.create(JcrLexicon.PREDECESSORS, newPreds);
507
508 Graph graph = workspace().graph();
509 Location location = Location.create(node.uuid());
510 graph.set(isCheckedOut, predecessors, multiValuedProps).on(location).and();
511
512 cache().refreshProperties(location);
513 }
514
515
516
517
518
519
520
521
522
523
524 @Override
525 public void restore( Version[] versions,
526 boolean removeExisting ) throws RepositoryException {
527 session.checkLive();
528 if (session.hasPendingChanges()) {
529 throw new InvalidItemStateException(JcrI18n.noPendingChangesAllowed.text());
530 }
531
532 Map<Version, AbstractJcrNode> existingVersions = new HashMap<Version, AbstractJcrNode>(versions.length);
533 Set<Path> versionRootPaths = new HashSet<Path>(versions.length);
534 List<Version> nonExistingVersions = new ArrayList<Version>(versions.length);
535
536 for (int i = 0; i < versions.length; i++) {
537 VersionHistory history = versions[i].getContainingHistory();
538
539 if (history.getRootVersion().isSame(versions[i])) {
540 throw new VersionException(JcrI18n.cannotRestoreRootVersion.text(versions[i].getPath()));
541 }
542
543 try {
544 AbstractJcrNode existingNode = session.getNodeByIdentifier(history.getVersionableIdentifier());
545 existingVersions.put(versions[i], existingNode);
546 versionRootPaths.add(existingNode.path());
547 } catch (ItemNotFoundException infe) {
548 nonExistingVersions.add(versions[i]);
549 }
550 }
551
552 if (existingVersions.isEmpty()) {
553 throw new VersionException(JcrI18n.noExistingVersionForRestore.text());
554 }
555
556 RestoreCommand op = new RestoreCommand(existingVersions, versionRootPaths, nonExistingVersions, null, removeExisting);
557 op.execute();
558
559 session.save();
560 }
561
562
563
564
565
566
567
568
569
570
571
572
573
574 void restore( Path path,
575 Version version,
576 String labelToRestore,
577 boolean removeExisting ) throws RepositoryException {
578 session.checkLive();
579
580 if (session().hasPendingChanges()) {
581 throw new InvalidItemStateException(JcrI18n.noPendingChangesAllowed.text());
582 }
583
584
585 AbstractJcrNode parentNode = cache().findJcrNode(null, path.getParent());
586 AbstractJcrNode existingNode = null;
587 AbstractJcrNode nodeToCheckLock;
588
589 JcrVersionNode jcrVersion = (JcrVersionNode)version;
590
591 try {
592 existingNode = cache().findJcrNode(null, path);
593 nodeToCheckLock = existingNode;
594
595
596 JcrVersionHistoryNode versionHistory = existingNode.getVersionHistory();
597 if (!versionHistory.isSame(jcrVersion.getParent())) {
598 throw new VersionException(JcrI18n.invalidVersion.text(version.getPath(), versionHistory.getPath()));
599 }
600
601 if (!versionHistory.isSame(existingNode.getVersionHistory())) {
602 throw new VersionException(JcrI18n.invalidVersion.text(version.getPath(), existingNode.getVersionHistory()
603 .getPath()));
604 }
605
606 if (jcrVersion.isSame(versionHistory.getRootVersion())) {
607 throw new VersionException(JcrI18n.cannotRestoreRootVersion.text(existingNode.getPath()));
608 }
609
610 } catch (ItemNotFoundException pnfe) {
611
612 if (!parentNode.isCheckedOut()) {
613 String parentPath = path.getString(context().getNamespaceRegistry());
614 throw new VersionException(JcrI18n.nodeIsCheckedIn.text(parentPath));
615 }
616
617 AbstractJcrNode sourceNode = frozenNodeFor(version);
618 Name primaryTypeName = name(sourceNode.getProperty(JcrLexicon.FROZEN_PRIMARY_TYPE).property().getFirstValue());
619 AbstractJcrProperty uuidProp = sourceNode.getProperty(JcrLexicon.FROZEN_UUID);
620 UUID desiredUuid = uuid(uuidProp.property().getFirstValue());
621
622 existingNode = parentNode.editor().createChild(path.getLastSegment().getName(), desiredUuid, primaryTypeName);
623
624 nodeToCheckLock = parentNode;
625 }
626
627 if (nodeToCheckLock.isLocked() && !nodeToCheckLock.holdsLock()) {
628 throw new LockException(JcrI18n.lockTokenNotHeld.text(nodeToCheckLock.getPath()));
629 }
630
631 RestoreCommand op = new RestoreCommand(Collections.singletonMap(version, existingNode),
632 Collections.singleton(existingNode.path()), Collections.<Version>emptySet(),
633 labelToRestore, removeExisting);
634 op.execute();
635
636 NodeEditor editor = existingNode.editor();
637 editor.setProperty(JcrLexicon.IS_CHECKED_OUT, existingNode.valueFrom(PropertyType.BOOLEAN, false), false, false);
638 editor.setProperty(JcrLexicon.BASE_VERSION, existingNode.valueFrom(jcrVersion), false, false);
639
640 session().save();
641
642 }
643
644
645
646
647
648
649 AbstractJcrNode frozenNodeFor( Version version ) throws RepositoryException {
650 return ((AbstractJcrNode)version).getNode(JcrLexicon.FROZEN_NODE);
651 }
652
653 void doneMerge( AbstractJcrNode targetNode,
654 Version version ) throws RepositoryException {
655 session.checkLive();
656 checkVersionable(targetNode);
657
658 if (targetNode.isNew() || targetNode.isModified()) {
659 throw new InvalidItemStateException(JcrI18n.noPendingChangesAllowedForNode.text());
660 }
661
662 if (!targetNode.isNodeType(JcrMixLexicon.VERSIONABLE)) {
663 throw new VersionException(JcrI18n.requiresVersionable.text());
664 }
665
666 AbstractJcrProperty prop = targetNode.getProperty(JcrLexicon.PREDECESSORS);
667
668 JcrValue[] values = (JcrValue[])prop.getValues();
669 JcrValue[] newValues = new JcrValue[values.length + 1];
670 System.arraycopy(values, 0, newValues, 0, values.length);
671 newValues[values.length] = targetNode.valueFrom(version);
672
673 targetNode.editor().setProperty(JcrLexicon.PREDECESSORS, newValues, PropertyType.REFERENCE, false);
674
675 removeVersionFromMergeFailedProperty(targetNode, version);
676
677 targetNode.save();
678 }
679
680 void cancelMerge( AbstractJcrNode targetNode,
681 Version version ) throws RepositoryException {
682 session.checkLive();
683 checkVersionable(targetNode);
684
685 if (targetNode.isNew() || targetNode.isModified()) {
686 throw new InvalidItemStateException(JcrI18n.noPendingChangesAllowedForNode.text());
687 }
688
689 if (!targetNode.isNodeType(JcrMixLexicon.VERSIONABLE)) {
690 throw new UnsupportedRepositoryOperationException(JcrI18n.requiresVersionable.text());
691 }
692
693 removeVersionFromMergeFailedProperty(targetNode, version);
694
695 targetNode.save();
696 }
697
698 @SuppressWarnings( "deprecation" )
699 private void removeVersionFromMergeFailedProperty( AbstractJcrNode targetNode,
700 Version version ) throws RepositoryException {
701
702 if (!targetNode.hasProperty(JcrLexicon.MERGE_FAILED)) {
703 throw new VersionException(JcrI18n.versionNotInMergeFailed.text(version.getName(), targetNode.getPath()));
704 }
705
706 AbstractJcrProperty prop = targetNode.getProperty(JcrLexicon.MERGE_FAILED);
707 Value[] values = prop.getValues();
708
709 String uuidString = version.getUUID();
710 int matchIndex = -1;
711 for (int i = 0; i < values.length; i++) {
712 if (uuidString.equals(values[i].getString())) {
713 matchIndex = i;
714 break;
715 }
716 }
717
718 if (matchIndex == -1) {
719 throw new VersionException(JcrI18n.versionNotInMergeFailed.text(version.getName(), targetNode.getPath()));
720 }
721
722 if (values.length == 1) {
723 prop.remove();
724 } else {
725 Value[] newValues = new JcrValue[values.length - 2];
726
727 if (matchIndex == 0) {
728 System.arraycopy(values, 1, newValues, 0, values.length - 1);
729 } else if (matchIndex == values.length - 1) {
730 System.arraycopy(values, 0, newValues, 0, values.length - 2);
731 } else {
732 System.arraycopy(values, 0, newValues, 0, matchIndex);
733 System.arraycopy(values, matchIndex + 1, newValues, matchIndex, values.length - matchIndex - 1);
734 }
735
736 prop.setValue(newValues);
737 }
738
739 }
740
741 NodeIterator merge( AbstractJcrNode targetNode,
742 String srcWorkspace,
743 boolean bestEffort,
744 boolean isShallow ) throws RepositoryException {
745 session.checkLive();
746
747 if (session().hasPendingChanges()) {
748 throw new InvalidItemStateException(JcrI18n.noPendingChangesAllowed.text());
749 }
750
751 try {
752 targetNode.correspondingNodePath(srcWorkspace);
753 } catch (ItemNotFoundException infe) {
754
755 return new JcrChildNodeIterator(Collections.<AbstractJcrNode>emptySet(), 0);
756 }
757
758 JcrSession sourceSession = session().with(srcWorkspace);
759 MergeCommand op = new MergeCommand(targetNode, sourceSession, bestEffort, isShallow);
760 op.execute();
761
762 session.save();
763
764 return op.getFailures();
765 }
766
767
768
769
770
771
772
773
774 void restoreProperty( AbstractJcrProperty property,
775 NodeEditor editor ) throws RepositoryException {
776 Name propName = property.name();
777 editor.removeProperty(propName);
778
779 if (property.isMultiple()) {
780 JcrValue[] values = (JcrValue[])property.getValues();
781 editor.setProperty(propName, values, property.getType(), false);
782 } else {
783 JcrValue value = (JcrValue)property.getValue();
784 editor.setProperty(propName, value, false, false);
785 }
786 }
787
788 void initializeVersionHistoryFor( AbstractJcrNode node ) throws RepositoryException {
789 initializeVersionHistoryFor(node, null);
790 }
791
792 void initializeVersionHistoryFor( AbstractJcrNode node,
793 UUID originalVersionUuid ) throws RepositoryException {
794 Batch batch = session().createBatch();
795
796 initializeVersionHistoryFor(batch, node.nodeInfo(), originalVersionUuid, true);
797
798 batch.execute();
799 }
800
801 void initializeVersionHistoryFor( Graph.Batch batch,
802 Node<JcrNodePayload, JcrPropertyPayload> node,
803 UUID originalVersionUuid,
804 boolean forceWrite ) throws RepositoryException {
805
806 if (!cache().isVersionable(node)) return;
807
808
809
810
811
812
813 boolean initialized = node.getProperty(JcrLexicon.IS_CHECKED_OUT) != null;
814 if (!forceWrite && initialized) return;
815
816 UUID historyUuid = UUID.randomUUID();
817 UUID versionUuid = UUID.randomUUID();
818
819 initializeVersionStorageFor(node, historyUuid, originalVersionUuid, versionUuid);
820
821 PropertyInfo<JcrPropertyPayload> jcrUuidProp = node.getProperty(JcrLexicon.UUID);
822 UUID jcrUuid = uuid(jcrUuidProp.getProperty().getFirstValue());
823 Path historyPath = versionHistoryPathFor(jcrUuid);
824
825 ValueFactory<Reference> refFactory = context().getValueFactories().getReferenceFactory();
826 org.modeshape.graph.property.Property isCheckedOut = propertyFactory().create(JcrLexicon.IS_CHECKED_OUT, true);
827 org.modeshape.graph.property.Property versionHistory = propertyFactory().create(JcrLexicon.VERSION_HISTORY,
828 refFactory.create(historyUuid));
829 org.modeshape.graph.property.Property baseVersion = propertyFactory().create(JcrLexicon.BASE_VERSION,
830 refFactory.create(versionUuid));
831 org.modeshape.graph.property.Property predecessors = propertyFactory().create(JcrLexicon.PREDECESSORS,
832 new Object[] {refFactory.create(versionUuid)});
833
834
835 batch.set(isCheckedOut, versionHistory, baseVersion, predecessors).on(node.getPath()).and();
836
837 Path storagePath = historyPath.getParent();
838 Node<JcrNodePayload, JcrPropertyPayload> storageNode = cache().findNode(null, storagePath);
839
840 cache().refresh(storageNode.getNodeId(), storagePath, false);
841 }
842
843 void initializeVersionStorageFor( Node<JcrNodePayload, JcrPropertyPayload> node,
844 UUID historyUuid,
845 UUID originalVersionUuid,
846 UUID versionUuid ) {
847 JcrNodePayload payload = node.getPayload();
848
849 Graph systemGraph = session().repository().createSystemGraph(context());
850 Batch systemBatch = systemGraph.batch();
851
852 Name primaryTypeName = payload.getPrimaryTypeName();
853 List<Name> mixinTypeNames = payload.getMixinTypeNames();
854
855 PropertyInfo<JcrPropertyPayload> jcrUuidProp = node.getProperty(JcrLexicon.UUID);
856
857 UUID jcrUuid = uuid(jcrUuidProp.getProperty().getFirstValue());
858 Path historyPath = versionHistoryPathFor(jcrUuid);
859
860 systemBatch.create(historyPath)
861 .with(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.VERSION_HISTORY)
862 .and(JcrLexicon.VERSIONABLE_UUID, jcrUuid)
863 .and(JcrLexicon.UUID, historyUuid)
864 .and();
865
866 if (originalVersionUuid != null) {
867 systemBatch.set(JcrLexicon.COPIED_FROM).on(historyPath).to(originalVersionUuid).and();
868 }
869
870 Path versionLabelsPath = path(historyPath, JcrLexicon.VERSION_LABELS);
871 systemBatch.create(versionLabelsPath).with(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.VERSION_LABELS).and();
872
873 Path rootVersionPath = path(historyPath, JcrLexicon.ROOT_VERSION);
874 DateTime now = context().getValueFactories().getDateFactory().create();
875 systemBatch.create(rootVersionPath)
876 .with(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.VERSION)
877 .and(JcrLexicon.CREATED, now)
878 .and(JcrLexicon.UUID, versionUuid)
879 .and();
880
881 Path frozenVersionPath = path(rootVersionPath, JcrLexicon.FROZEN_NODE);
882 systemBatch.create(frozenVersionPath)
883 .with(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.FROZEN_NODE)
884 .and(JcrLexicon.FROZEN_UUID, jcrUuid)
885 .and(JcrLexicon.FROZEN_PRIMARY_TYPE, primaryTypeName)
886 .and(JcrLexicon.FROZEN_MIXIN_TYPES, mixinTypeNames)
887 .and();
888
889 systemBatch.execute();
890
891 }
892
893 @NotThreadSafe
894 private class RestoreCommand {
895
896 private Map<Version, AbstractJcrNode> existingVersions;
897 private Set<Path> versionRootPaths;
898 private Collection<Version> nonExistingVersions;
899 private boolean removeExisting;
900 private String labelToRestore;
901 private Map<AbstractJcrNode, AbstractJcrNode> changedNodes;
902
903 public RestoreCommand( Map<Version, AbstractJcrNode> existingVersions,
904 Set<Path> versionRootPaths,
905 Collection<Version> nonExistingVersions,
906 String labelToRestore,
907 boolean removeExisting ) {
908 super();
909 this.existingVersions = existingVersions;
910 this.versionRootPaths = versionRootPaths;
911 this.nonExistingVersions = nonExistingVersions;
912 this.removeExisting = removeExisting;
913 this.labelToRestore = labelToRestore;
914
915
916 this.changedNodes = new HashMap<AbstractJcrNode, AbstractJcrNode>(100);
917 }
918
919 void execute() throws RepositoryException {
920 Collection<Version> versionsToCheck = new ArrayList<Version>(existingVersions.keySet());
921 for (Version version : versionsToCheck) {
922 AbstractJcrNode root = existingVersions.get(version);
923
924 if (root == null) continue;
925
926
927 restoreNodeMixins(frozenNodeFor(version), root);
928 restoreNode(frozenNodeFor(version), root, dateTime(version.getCreated()));
929 clearCheckoutStatus(frozenNodeFor(version), root);
930 }
931
932 if (!nonExistingVersions.isEmpty()) {
933 StringBuilder versions = new StringBuilder();
934 boolean first = true;
935 for (Version version : nonExistingVersions) {
936 if (!first) {
937 versions.append(", ");
938 } else {
939 first = false;
940 }
941 versions.append(version.getName());
942 }
943 throw new VersionException(JcrI18n.unrootedVersionsInRestore.text(versions.toString()));
944 }
945
946 for (Map.Entry<AbstractJcrNode, AbstractJcrNode> changedNode : changedNodes.entrySet()) {
947 restoreProperties(changedNode.getKey(), changedNode.getValue());
948 }
949 }
950
951
952
953
954
955
956
957
958
959
960
961
962
963 private void restoreNode( AbstractJcrNode sourceNode,
964 AbstractJcrNode targetNode,
965 DateTime checkinTime ) throws RepositoryException {
966 changedNodes.put(sourceNode, targetNode);
967
968 NodeEditor targetEditor = targetNode.editor();
969 Node<JcrNodePayload, JcrPropertyPayload> targetNodeInfo = targetNode.nodeInfo();
970 Node<JcrNodePayload, JcrPropertyPayload> sourceNodeInfo = sourceNode.nodeInfo();
971
972 Set<Node<JcrNodePayload, JcrPropertyPayload>> versionedChildrenThatShouldNotBeRestored = new HashSet<Node<JcrNodePayload, JcrPropertyPayload>>();
973
974
975 Map<Node<JcrNodePayload, JcrPropertyPayload>, Node<JcrNodePayload, JcrPropertyPayload>> presentInBoth = new HashMap<Node<JcrNodePayload, JcrPropertyPayload>, Node<JcrNodePayload, JcrPropertyPayload>>();
976
977
978 List<Node<JcrNodePayload, JcrPropertyPayload>> inTargetOnly = copyOf(targetNodeInfo.getChildren(),
979 targetNodeInfo.getChildrenCount());
980
981
982 Map<Node<JcrNodePayload, JcrPropertyPayload>, Node<JcrNodePayload, JcrPropertyPayload>> inSourceOnly = new HashMap<Node<JcrNodePayload, JcrPropertyPayload>, Node<JcrNodePayload, JcrPropertyPayload>>();
983
984
985 for (Node<JcrNodePayload, JcrPropertyPayload> sourceChild : sourceNodeInfo.getChildren()) {
986 boolean isVersionedChild = JcrNtLexicon.VERSIONED_CHILD.equals(name(sourceChild.getProperty(JcrLexicon.PRIMARY_TYPE)
987 .getProperty()
988 .getFirstValue()));
989 Node<JcrNodePayload, JcrPropertyPayload> resolvedNode = resolveSourceNode(sourceChild, checkinTime);
990 Node<JcrNodePayload, JcrPropertyPayload> match = findMatchFor(resolvedNode);
991
992 if (match != null) {
993 if (isVersionedChild) {
994 if (!removeExisting) {
995 Object rawUuid = match.getProperty(JcrLexicon.UUID).getProperty().getFirstValue();
996 String uuid = rawUuid == null ? null : rawUuid.toString();
997 throw new ItemExistsException(JcrI18n.itemAlreadyExistsWithUuid.text(uuid,
998 workspace().getName(),
999 match.getPath()));
1000 }
1001
1002 versionedChildrenThatShouldNotBeRestored.add(match);
1003 }
1004 inTargetOnly.remove(match);
1005 presentInBoth.put(sourceChild, match);
1006
1007 } else {
1008 inSourceOnly.put(sourceChild, resolvedNode);
1009 }
1010 }
1011
1012
1013 for (Node<JcrNodePayload, JcrPropertyPayload> targetChild : inTargetOnly) {
1014 switch (nodeDefinitionFor(targetChild).getOnParentVersion()) {
1015 case OnParentVersionAction.COPY:
1016 case OnParentVersionAction.ABORT:
1017 case OnParentVersionAction.VERSION:
1018 targetEditor.destroyChild(targetChild);
1019 break;
1020
1021 case OnParentVersionAction.COMPUTE:
1022
1023 case OnParentVersionAction.INITIALIZE:
1024 case OnParentVersionAction.IGNORE:
1025
1026 }
1027 }
1028
1029 LinkedList<Node<JcrNodePayload, JcrPropertyPayload>> reversedChildren = new LinkedList<Node<JcrNodePayload, JcrPropertyPayload>>();
1030 for (Node<JcrNodePayload, JcrPropertyPayload> sourceChild : sourceNodeInfo.getChildren()) {
1031 reversedChildren.addFirst(sourceChild);
1032 }
1033
1034
1035
1036 Node<JcrNodePayload, JcrPropertyPayload> prevChild = null;
1037 for (Node<JcrNodePayload, JcrPropertyPayload> sourceChild : reversedChildren) {
1038 Node<JcrNodePayload, JcrPropertyPayload> targetChild = presentInBoth.get(sourceChild);
1039 Node<JcrNodePayload, JcrPropertyPayload> resolvedChild;
1040
1041 AbstractJcrNode sourceChildNode;
1042 AbstractJcrNode targetChildNode;
1043
1044 boolean shouldRestore = !versionedChildrenThatShouldNotBeRestored.contains(targetChild);
1045
1046 if (targetChild != null) {
1047
1048 resolvedChild = resolveSourceNode(sourceChild, checkinTime);
1049
1050 sourceChildNode = cache().findJcrNode(resolvedChild.getNodeId(), resolvedChild.getPath());
1051 targetChildNode = cache().findJcrNode(targetChild.getNodeId(), targetChild.getPath());
1052
1053 } else {
1054
1055 resolvedChild = inSourceOnly.get(sourceChild);
1056 sourceChildNode = cache().findJcrNode(resolvedChild.getNodeId(), resolvedChild.getPath());
1057
1058 Name primaryTypeName = name(resolvedChild.getProperty(JcrLexicon.FROZEN_PRIMARY_TYPE)
1059 .getProperty()
1060 .getFirstValue());
1061 PropertyInfo<JcrPropertyPayload> uuidProp = resolvedChild.getProperty(JcrLexicon.FROZEN_UUID);
1062 UUID desiredUuid = uuid(uuidProp.getProperty().getFirstValue());
1063
1064 targetChildNode = targetEditor.createChild(sourceChild.getName(), desiredUuid, primaryTypeName);
1065
1066 assert shouldRestore == true;
1067 }
1068
1069 if (shouldRestore) {
1070
1071 restoreNodeMixins(sourceChildNode, targetChildNode);
1072
1073 if (sourceChildNode.getParent().isNodeType(JcrNtLexicon.VERSION)) {
1074 clearCheckoutStatus(sourceChildNode, targetChildNode);
1075 }
1076 restoreNode(sourceChildNode, targetChildNode, checkinTime);
1077 }
1078
1079 orderBefore(sourceChild, prevChild, targetEditor);
1080 prevChild = sourceChild;
1081 }
1082 }
1083
1084 private void clearCheckoutStatus( AbstractJcrNode sourceChildNode,
1085 AbstractJcrNode targetChildNode ) throws RepositoryException {
1086
1087 NodeEditor editor = targetChildNode.editor();
1088 editor.setProperty(JcrLexicon.IS_CHECKED_OUT, targetChildNode.valueFrom(PropertyType.BOOLEAN, false), false, false);
1089 editor.setProperty(JcrLexicon.BASE_VERSION, targetChildNode.valueFrom(sourceChildNode.getParent()), false, false);
1090
1091 }
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110 private void orderBefore( Node<JcrNodePayload, JcrPropertyPayload> targetNode,
1111 Node<JcrNodePayload, JcrPropertyPayload> beforeNode,
1112 NodeEditor parentEditor ) throws RepositoryException {
1113 Segment beforeSegment = beforeNode == null ? null : beforeNode.getSegment();
1114
1115 parentEditor.orderChildBefore(targetNode.getSegment(), beforeSegment);
1116
1117 }
1118
1119
1120
1121
1122
1123
1124
1125
1126 private void restoreNodeMixins( AbstractJcrNode sourceNode,
1127 AbstractJcrNode targetNode ) throws RepositoryException {
1128 AbstractJcrProperty mixinTypesProp = sourceNode.getProperty(JcrLexicon.FROZEN_MIXIN_TYPES);
1129 NodeEditor childEditor = targetNode.editor();
1130 Object[] mixinTypeNames = mixinTypesProp == null ? EMPTY_OBJECT_ARRAY : mixinTypesProp.property().getValuesAsArray();
1131
1132 Collection<Name> currentMixinTypes = new HashSet<Name>(targetNode.getMixinTypeNames());
1133
1134 for (int i = 0; i < mixinTypeNames.length; i++) {
1135 Name mixinTypeName = name(mixinTypeNames[i]);
1136
1137 if (!currentMixinTypes.remove(mixinTypeName)) {
1138 JcrNodeType mixinType = session().nodeTypeManager().getNodeType(mixinTypeName);
1139 childEditor.addMixin(mixinType);
1140 }
1141 }
1142
1143 }
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153 private void restoreProperties( AbstractJcrNode sourceNode,
1154 AbstractJcrNode targetNode ) throws RepositoryException {
1155 NodeEditor childEditor = targetNode.editor();
1156 Map<Name, PropertyInfo<JcrPropertyPayload>> sourcePropertyNames = new HashMap<Name, PropertyInfo<JcrPropertyPayload>>();
1157 for (PropertyInfo<JcrPropertyPayload> propInfo : sourceNode.nodeInfo().getProperties()) {
1158 if (!IGNORED_PROP_NAMES_FOR_RESTORE.contains(propInfo.getName())) {
1159 sourcePropertyNames.put(propInfo.getName(), propInfo);
1160 }
1161 }
1162
1163 Collection<PropertyInfo<JcrPropertyPayload>> targetProps = new ArrayList<PropertyInfo<JcrPropertyPayload>>(
1164 targetNode.nodeInfo()
1165 .getProperties());
1166 for (PropertyInfo<JcrPropertyPayload> propInfo : targetProps) {
1167 Name propName = propInfo.getName();
1168
1169 if (sourcePropertyNames.containsKey(propName)) {
1170
1171 restoreProperty(sourcePropertyNames.get(propName).getPayload().getJcrProperty(), childEditor);
1172 sourcePropertyNames.remove(propName);
1173 } else {
1174 PropertyDefinitionId propDefnId = propInfo.getPayload().getPropertyDefinitionId();
1175 PropertyDefinition propDefn = session().nodeTypeManager().getPropertyDefinition(propDefnId);
1176
1177 switch (propDefn.getOnParentVersion()) {
1178 case OnParentVersionAction.COPY:
1179 case OnParentVersionAction.ABORT:
1180 case OnParentVersionAction.VERSION:
1181 childEditor.removeProperty(propName);
1182 break;
1183
1184 case OnParentVersionAction.COMPUTE:
1185 case OnParentVersionAction.INITIALIZE:
1186 case OnParentVersionAction.IGNORE:
1187
1188 }
1189 }
1190 }
1191
1192 for (Map.Entry<Name, PropertyInfo<JcrPropertyPayload>> sourceProperty : sourcePropertyNames.entrySet()) {
1193 restoreProperty(sourceProperty.getValue().getPayload().getJcrProperty(), childEditor);
1194 }
1195 }
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210 private Node<JcrNodePayload, JcrPropertyPayload> resolveSourceNode( Node<JcrNodePayload, JcrPropertyPayload> sourceNode,
1211 DateTime checkinTime ) throws RepositoryException {
1212 Name sourcePrimaryTypeName = name(sourceNode.getProperty(JcrLexicon.PRIMARY_TYPE).getProperty().getFirstValue());
1213
1214 if (JcrNtLexicon.FROZEN_NODE.equals(sourcePrimaryTypeName)) return sourceNode;
1215 assert JcrNtLexicon.VERSIONED_CHILD.equals(sourcePrimaryTypeName);
1216
1217
1218 PropertyInfo<JcrPropertyPayload> historyUuidProp = sourceNode.getProperty(JcrLexicon.CHILD_VERSION_HISTORY);
1219 UUID uuid = uuid(historyUuidProp.getProperty().getFirstValue());
1220 assert uuid != null;
1221 String uuidString = uuid.toString();
1222
1223
1224
1225
1226 for (Version version : nonExistingVersions) {
1227 if (uuidString.equals(version.getContainingHistory().getIdentifier())) {
1228 JcrVersionNode versionNode = (JcrVersionNode)version;
1229 nonExistingVersions.remove(version);
1230 return versionNode.getFrozenNode().nodeInfo();
1231 }
1232 }
1233
1234
1235
1236
1237 for (Version version : existingVersions.keySet()) {
1238 if (uuidString.equals(version.getContainingHistory().getIdentifier())) {
1239 JcrVersionNode versionNode = (JcrVersionNode)version;
1240 existingVersions.remove(version);
1241 return versionNode.getFrozenNode().nodeInfo();
1242 }
1243 }
1244
1245
1246
1247
1248 JcrVersionHistoryNode versionHistory = (JcrVersionHistoryNode)cache().findJcrNode(Location.create(uuid));
1249
1250 if (labelToRestore != null) {
1251 try {
1252 JcrVersionNode versionNode = versionHistory.getVersionByLabel(labelToRestore);
1253 return versionNode.getFrozenNode().nodeInfo();
1254 } catch (VersionException noVersionWithThatLabel) {
1255
1256 }
1257 }
1258
1259
1260
1261
1262 AbstractJcrNode match = closestMatchFor(versionHistory, checkinTime);
1263
1264 return match.nodeInfo();
1265 }
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281 private Node<JcrNodePayload, JcrPropertyPayload> findMatchFor( Node<JcrNodePayload, JcrPropertyPayload> sourceNode )
1282 throws ItemExistsException, RepositoryException {
1283
1284 PropertyInfo<JcrPropertyPayload> uuidProp = sourceNode.getProperty(JcrLexicon.FROZEN_UUID);
1285 UUID sourceUuid = uuid(uuidProp.getProperty().getFirstValue());
1286
1287 try {
1288 AbstractJcrNode match = cache().findJcrNode(Location.create(sourceUuid));
1289
1290 if (nodeIsOutsideRestoredForest(match)) return null;
1291
1292 return match.nodeInfo();
1293 } catch (ItemNotFoundException infe) {
1294 return null;
1295 }
1296 }
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306 private List<Node<JcrNodePayload, JcrPropertyPayload>> copyOf( Iterable<Node<JcrNodePayload, JcrPropertyPayload>> rawElements,
1307 int size ) {
1308 List<Node<JcrNodePayload, JcrPropertyPayload>> newList = new ArrayList<Node<JcrNodePayload, JcrPropertyPayload>>(size);
1309 for (Node<JcrNodePayload, JcrPropertyPayload> node : rawElements) {
1310 newList.add(node);
1311 }
1312 return newList;
1313 }
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326 private boolean nodeIsOutsideRestoredForest( AbstractJcrNode node ) throws ItemExistsException, RepositoryException {
1327 Path nodePath = node.path();
1328
1329 for (Path rootPath : versionRootPaths) {
1330 if (nodePath.isAtOrBelow(rootPath)) return false;
1331 }
1332
1333 if (!removeExisting) {
1334 throw new ItemExistsException(JcrI18n.itemAlreadyExistsWithUuid.text(node.uuid(),
1335 workspace().getName(),
1336 node.getPath()));
1337 }
1338
1339 node.remove();
1340 return true;
1341 }
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353 private AbstractJcrNode closestMatchFor( JcrVersionHistoryNode versionHistory,
1354 DateTime checkinTime ) throws RepositoryException {
1355 DateTimeFactory dateFactory = context().getValueFactories().getDateFactory();
1356
1357 VersionIterator iter = versionHistory.getAllVersions();
1358 Map<DateTime, Version> versions = new HashMap<DateTime, Version>((int)iter.getSize());
1359
1360 while (iter.hasNext()) {
1361 Version version = iter.nextVersion();
1362 versions.put(dateFactory.create(version.getCreated()), version);
1363 }
1364
1365 List<DateTime> versionDates = new ArrayList<DateTime>(versions.keySet());
1366 Collections.sort(versionDates);
1367
1368 for (int i = versionDates.size() - 1; i >= 0; i--) {
1369 if (versionDates.get(i).isBefore(checkinTime)) {
1370 Version version = versions.get(versionDates.get(i));
1371 return ((JcrVersionNode)version).getFrozenNode();
1372 }
1373 }
1374
1375 throw new IllegalStateException("First checkin must be before the checkin time of the node to be restored");
1376 }
1377 }
1378
1379 @NotThreadSafe
1380 private class MergeCommand {
1381 private final Collection<AbstractJcrNode> failures;
1382 private final AbstractJcrNode targetNode;
1383 private final boolean bestEffort;
1384 private final boolean isShallow;
1385 private final JcrSession sourceSession;
1386 private final String workspaceName;
1387
1388 public MergeCommand( AbstractJcrNode targetNode,
1389 JcrSession sourceSession,
1390 boolean bestEffort,
1391 boolean isShallow ) {
1392 super();
1393 this.targetNode = targetNode;
1394 this.sourceSession = sourceSession;
1395 this.bestEffort = bestEffort;
1396 this.isShallow = isShallow;
1397
1398 this.workspaceName = sourceSession.getWorkspace().getName();
1399 this.failures = new LinkedList<AbstractJcrNode>();
1400 }
1401
1402 final JcrChildNodeIterator getFailures() {
1403 return new JcrChildNodeIterator(failures, failures.size());
1404 }
1405
1406 void execute() throws RepositoryException {
1407 doMerge(targetNode);
1408 }
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422 private void doMerge( AbstractJcrNode targetNode ) throws RepositoryException {
1423
1424
1425 Path sourcePath = targetNode.correspondingNodePath(workspaceName);
1426
1427 AbstractJcrNode sourceNode;
1428 try {
1429 sourceNode = sourceSession.getNode(sourcePath);
1430 } catch (ItemNotFoundException infe) {
1431 doLeave(targetNode);
1432 return;
1433 }
1434
1435 if (!targetNode.isNodeType(JcrMixLexicon.VERSIONABLE)) {
1436 doUpdate(targetNode, sourceNode);
1437 return;
1438 } else if (!sourceNode.isNodeType(JcrMixLexicon.VERSIONABLE)) {
1439 doLeave(targetNode);
1440 return;
1441 }
1442
1443 JcrVersionNode sourceVersion = sourceNode.getBaseVersion();
1444 JcrVersionNode targetVersion = targetNode.getBaseVersion();
1445
1446 if (sourceVersion.isSuccessorOf(targetVersion) && !targetNode.isCheckedOut()) {
1447 doUpdate(targetNode, sourceNode);
1448 return;
1449 }
1450
1451 if (targetVersion.isSuccessorOf(sourceVersion) || targetVersion.uuid().equals(sourceVersion.uuid())) {
1452 doLeave(targetNode);
1453 return;
1454 }
1455
1456 doFail(targetNode, sourceVersion);
1457 }
1458
1459
1460
1461
1462
1463 private void doLeave( AbstractJcrNode targetNode ) throws RepositoryException {
1464 if (isShallow == false) {
1465
1466 for (NodeIterator iter = targetNode.getNodes(); iter.hasNext();) {
1467 doMerge((AbstractJcrNode)iter.nextNode());
1468 }
1469 }
1470 }
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486 private void doUpdate( AbstractJcrNode targetNode,
1487 AbstractJcrNode sourceNode ) throws RepositoryException {
1488 restoreProperties(sourceNode, targetNode);
1489
1490 LinkedHashMap<String, AbstractJcrNode> sourceNodes = childNodeMapFor(sourceNode);
1491 LinkedHashMap<String, AbstractJcrNode> targetNodes = childNodeMapFor(targetNode);
1492
1493
1494 Map<String, AbstractJcrNode> sourceOnly = new LinkedHashMap<String, AbstractJcrNode>(sourceNodes);
1495 sourceOnly.keySet().removeAll(targetNodes.keySet());
1496
1497 for (AbstractJcrNode node : sourceOnly.values()) {
1498 workspace().copy(workspaceName, node.getPath(), targetNode.getPath() + "/" + node.getName());
1499 }
1500
1501
1502 LinkedHashMap<String, AbstractJcrNode> targetOnly = new LinkedHashMap<String, AbstractJcrNode>(targetNodes);
1503 targetOnly.keySet().removeAll(targetOnly.keySet());
1504
1505 for (AbstractJcrNode node : targetOnly.values()) {
1506 node.remove();
1507 }
1508
1509
1510 Map<String, AbstractJcrNode> presentInBoth = new HashMap<String, AbstractJcrNode>(targetNodes);
1511 presentInBoth.keySet().retainAll(sourceNodes.keySet());
1512 for (AbstractJcrNode node : presentInBoth.values()) {
1513 if (isShallow && node.isNodeType(JcrMixLexicon.VERSIONABLE)) continue;
1514 doMerge(node);
1515 }
1516 }
1517
1518 private LinkedHashMap<String, AbstractJcrNode> childNodeMapFor( AbstractJcrNode node ) throws RepositoryException {
1519 LinkedHashMap<String, AbstractJcrNode> childNodes = new LinkedHashMap<String, AbstractJcrNode>();
1520
1521 for (NodeIterator iter = node.getNodes(); iter.hasNext();) {
1522 AbstractJcrNode child = (AbstractJcrNode)iter.nextNode();
1523 childNodes.put(child.getName(), child);
1524 }
1525
1526 return childNodes;
1527 }
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538 private void doFail( AbstractJcrNode targetNode,
1539 JcrVersionNode sourceVersion ) throws RepositoryException {
1540 if (!bestEffort) {
1541 throw new MergeException();
1542 }
1543
1544 NodeEditor targetEditor = targetNode.editor();
1545 if (targetNode.hasProperty(JcrLexicon.MERGE_FAILED)) {
1546 JcrValue[] existingValues = (JcrValue[])targetNode.getProperty(JcrLexicon.MERGE_FAILED).getValues();
1547
1548 boolean found = false;
1549 String sourceUuidString = sourceVersion.uuid().toString();
1550 for (int i = 0; i < existingValues.length; i++) {
1551 if (sourceUuidString.equals(existingValues[i].getString())) {
1552 found = true;
1553 break;
1554 }
1555 }
1556
1557 if (!found) {
1558 JcrValue[] newValues = new JcrValue[existingValues.length + 1];
1559 System.arraycopy(existingValues, 0, newValues, 0, existingValues.length);
1560 newValues[newValues.length - 1] = targetNode.valueFrom(sourceVersion);
1561 targetEditor.setProperty(JcrLexicon.MERGE_FAILED, newValues, PropertyType.REFERENCE, false);
1562 }
1563
1564 } else {
1565 targetEditor.setProperty(JcrLexicon.MERGE_FAILED, targetNode.valueFrom(sourceVersion), false, false);
1566 }
1567 failures.add(targetNode);
1568
1569 if (isShallow == false) {
1570 for (NodeIterator iter = targetNode.getNodes(); iter.hasNext();) {
1571 AbstractJcrNode childNode = (AbstractJcrNode)iter.nextNode();
1572
1573 if (childNode.isNodeType(JcrMixLexicon.VERSIONABLE)) {
1574 doMerge(childNode);
1575 }
1576 }
1577 }
1578 }
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588 private void restoreProperties( AbstractJcrNode sourceNode,
1589 AbstractJcrNode targetNode ) throws RepositoryException {
1590 NodeEditor childEditor = targetNode.editor();
1591 Map<Name, PropertyInfo<JcrPropertyPayload>> sourcePropertyNames = new HashMap<Name, PropertyInfo<JcrPropertyPayload>>();
1592 for (PropertyInfo<JcrPropertyPayload> propInfo : sourceNode.nodeInfo().getProperties()) {
1593 if (!IGNORED_PROP_NAMES_FOR_RESTORE.contains(propInfo.getName())) {
1594 sourcePropertyNames.put(propInfo.getName(), propInfo);
1595 }
1596 }
1597
1598 Collection<PropertyInfo<JcrPropertyPayload>> targetProps = new ArrayList<PropertyInfo<JcrPropertyPayload>>(
1599 targetNode.nodeInfo()
1600 .getProperties());
1601 for (PropertyInfo<JcrPropertyPayload> propInfo : targetProps) {
1602 Name propName = propInfo.getName();
1603
1604 if (sourcePropertyNames.containsKey(propName)) {
1605
1606 restoreProperty(sourcePropertyNames.get(propName).getPayload().getJcrProperty(), childEditor);
1607 sourcePropertyNames.remove(propName);
1608 } else {
1609 PropertyDefinitionId propDefnId = propInfo.getPayload().getPropertyDefinitionId();
1610 PropertyDefinition propDefn = session().nodeTypeManager().getPropertyDefinition(propDefnId);
1611
1612 switch (propDefn.getOnParentVersion()) {
1613 case OnParentVersionAction.COPY:
1614 case OnParentVersionAction.ABORT:
1615 case OnParentVersionAction.VERSION:
1616 childEditor.removeProperty(propName);
1617 break;
1618
1619 case OnParentVersionAction.COMPUTE:
1620 case OnParentVersionAction.INITIALIZE:
1621 case OnParentVersionAction.IGNORE:
1622
1623 }
1624 }
1625 }
1626
1627 for (Map.Entry<Name, PropertyInfo<JcrPropertyPayload>> sourceProperty : sourcePropertyNames.entrySet()) {
1628 restoreProperty(sourceProperty.getValue().getPayload().getJcrProperty(), childEditor);
1629 }
1630 }
1631
1632 }
1633
1634 @Override
1635 public void cancelMerge( String absPath,
1636 Version version ) throws VersionException, InvalidItemStateException, RepositoryException {
1637 cancelMerge(session.getNode(absPath), version);
1638 }
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648 private void checkVersionable( AbstractJcrNode node ) throws UnsupportedRepositoryOperationException, RepositoryException {
1649 if (!node.isNodeType(JcrMixLexicon.VERSIONABLE)) {
1650 throw new UnsupportedRepositoryOperationException(JcrI18n.requiresVersionable.text());
1651 }
1652 }
1653
1654 @Override
1655 public Version checkin( String absPath )
1656 throws VersionException, InvalidItemStateException, LockException, RepositoryException {
1657 return checkin(session.getNode(absPath));
1658 }
1659
1660 @Override
1661 public void checkout( String absPath ) throws LockException, RepositoryException {
1662 checkout(session.getNode(absPath));
1663 }
1664
1665 @Override
1666 public Version checkpoint( String absPath )
1667 throws VersionException, InvalidItemStateException, LockException, RepositoryException {
1668 Version version = checkin(absPath);
1669 checkout(absPath);
1670 return version;
1671 }
1672
1673 @Override
1674 public void doneMerge( String absPath,
1675 Version version ) throws VersionException, InvalidItemStateException, RepositoryException {
1676 doneMerge(session.getNode(absPath), version);
1677 }
1678
1679 @Override
1680 public javax.jcr.Node getActivity() throws UnsupportedRepositoryOperationException, RepositoryException {
1681 throw new UnsupportedRepositoryOperationException();
1682 }
1683
1684 @Override
1685 public Version getBaseVersion( String absPath ) throws RepositoryException {
1686 return session.getNode(absPath).getBaseVersion();
1687 }
1688
1689 @Override
1690 public VersionHistory getVersionHistory( String absPath ) throws UnsupportedRepositoryOperationException, RepositoryException {
1691 return session.getNode(absPath).getVersionHistory();
1692 }
1693
1694 @Override
1695 public boolean isCheckedOut( String absPath ) throws RepositoryException {
1696 AbstractJcrNode node = session.getNode(absPath);
1697 return node.isCheckedOut();
1698 }
1699
1700 @SuppressWarnings( "unused" )
1701 @Override
1702 public NodeIterator merge( javax.jcr.Node activityNode )
1703 throws VersionException, AccessDeniedException, MergeException, LockException, InvalidItemStateException,
1704 RepositoryException {
1705 throw new UnsupportedRepositoryOperationException();
1706 }
1707
1708 @Override
1709 public NodeIterator merge( String absPath,
1710 String srcWorkspace,
1711 boolean bestEffort,
1712 boolean isShallow )
1713 throws NoSuchWorkspaceException, AccessDeniedException, MergeException, LockException, InvalidItemStateException,
1714 RepositoryException {
1715 CheckArg.isNotNull(srcWorkspace, "source workspace name");
1716
1717 AbstractJcrNode node = session.getNode(absPath);
1718
1719 return merge(node, srcWorkspace, bestEffort, isShallow);
1720 }
1721
1722 @Override
1723 public NodeIterator merge( String absPath,
1724 String srcWorkspace,
1725 boolean bestEffort )
1726 throws NoSuchWorkspaceException, AccessDeniedException, MergeException, LockException, InvalidItemStateException,
1727 RepositoryException {
1728 return merge(absPath, srcWorkspace, bestEffort, false);
1729 }
1730
1731 @Override
1732 public void restore( String absPath,
1733 String versionName,
1734 boolean removeExisting )
1735 throws VersionException, ItemExistsException, LockException, InvalidItemStateException, RepositoryException {
1736
1737 Version version = null;
1738
1739
1740 Path path = session.pathFor(absPath, "absPath");
1741
1742 AbstractJcrNode existingNode = session.getNode(path);
1743 VersionHistory historyNode = existingNode.getVersionHistory();
1744 if (historyNode != null) {
1745 version = historyNode.getVersion(versionName);
1746 }
1747
1748 assert version != null;
1749
1750
1751
1752
1753 restore(path, version, null, removeExisting);
1754 }
1755
1756 @Override
1757 public void restore( String absPath,
1758 Version version,
1759 boolean removeExisting )
1760 throws PathNotFoundException, ItemExistsException, VersionException, ConstraintViolationException, LockException,
1761 InvalidItemStateException, RepositoryException {
1762 Path path = session.pathFor(absPath, "absPath");
1763
1764 restore(path, version, null, removeExisting);
1765 }
1766
1767 @Override
1768 public void restore( Version version,
1769 boolean removeExisting )
1770 throws VersionException, ItemExistsException, InvalidItemStateException, LockException, RepositoryException {
1771 AbstractJcrNode node = session.getNodeByIdentifier(version.getContainingHistory().getVersionableIdentifier());
1772 Path path = node.path();
1773
1774 restore(path, version, null, removeExisting);
1775 }
1776
1777 @Override
1778 public void restoreByLabel( String absPath,
1779 String versionLabel,
1780 boolean removeExisting )
1781 throws VersionException, ItemExistsException, LockException, InvalidItemStateException, RepositoryException {
1782 session.getNode(absPath).restoreByLabel(versionLabel, removeExisting);
1783 }
1784
1785 @Override
1786 public javax.jcr.Node setActivity( javax.jcr.Node activity )
1787 throws UnsupportedRepositoryOperationException, RepositoryException {
1788 throw new UnsupportedRepositoryOperationException();
1789 }
1790
1791 @SuppressWarnings( "unused" )
1792 @Override
1793 public void removeActivity( javax.jcr.Node activityNode )
1794 throws UnsupportedRepositoryOperationException, VersionException, RepositoryException {
1795 throw new UnsupportedRepositoryOperationException();
1796 }
1797
1798 @Override
1799 public javax.jcr.Node createActivity( String title ) throws UnsupportedRepositoryOperationException, RepositoryException {
1800 throw new UnsupportedRepositoryOperationException();
1801 }
1802
1803 @Override
1804 public javax.jcr.Node createConfiguration( String absPath )
1805 throws UnsupportedRepositoryOperationException, RepositoryException {
1806 throw new UnsupportedRepositoryOperationException();
1807 }
1808
1809 }