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