1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.cms.core.version;
35
36 import info.magnolia.cms.core.Content;
37 import info.magnolia.cms.core.ItemType;
38 import info.magnolia.cms.core.MgnlNodeType;
39 import info.magnolia.cms.core.Path;
40 import info.magnolia.cms.security.JCRSessionOp;
41 import info.magnolia.cms.security.PermissionUtil;
42 import info.magnolia.cms.util.ExclusiveWrite;
43 import info.magnolia.cms.util.Rule;
44 import info.magnolia.context.MgnlContext;
45 import info.magnolia.jcr.predicate.RuleBasedNodePredicate;
46 import info.magnolia.jcr.util.MetaDataUtil;
47 import info.magnolia.jcr.util.NodeUtil;
48 import info.magnolia.jcr.util.VersionUtil;
49
50 import java.io.ByteArrayInputStream;
51 import java.io.ByteArrayOutputStream;
52 import java.io.IOException;
53 import java.io.ObjectInput;
54 import java.io.ObjectInputStream;
55 import java.io.ObjectOutput;
56 import java.io.ObjectOutputStream;
57 import java.util.ArrayList;
58 import java.util.Iterator;
59 import java.util.List;
60
61 import javax.jcr.ItemNotFoundException;
62 import javax.jcr.LoginException;
63 import javax.jcr.Node;
64 import javax.jcr.NodeIterator;
65 import javax.jcr.PathNotFoundException;
66 import javax.jcr.RepositoryException;
67 import javax.jcr.Session;
68 import javax.jcr.UnsupportedRepositoryOperationException;
69 import javax.jcr.Value;
70 import javax.jcr.nodetype.NodeType;
71 import javax.jcr.version.Version;
72 import javax.jcr.version.VersionException;
73 import javax.jcr.version.VersionHistory;
74 import javax.jcr.version.VersionIterator;
75
76 import org.apache.commons.codec.binary.Base64;
77 import org.apache.commons.io.IOUtils;
78 import org.slf4j.Logger;
79 import org.slf4j.LoggerFactory;
80
81
82
83
84
85
86
87
88
89
90 public abstract class BaseVersionManager {
91
92
93
94
95 public static final String VERSION_WORKSPACE = "mgnlVersion";
96
97
98
99
100 public static final String TMP_REFERENCED_NODES = "mgnl:tmpReferencedNodes";
101
102
103
104
105 protected static final String SYSTEM_NODE = "mgnl:versionMetaData";
106
107
108
109
110 public static final String PROPERTY_RULE = "Rule";
111
112
113
114
115 protected static final String ROOT_VERSION = "jcr:rootVersion";
116
117 private static Logger log = LoggerFactory.getLogger(BaseVersionManager.class);
118
119
120
121
122
123 protected void createInitialStructure() throws RepositoryException {
124 MgnlContext.doInSystemContext(new JCRSessionOp<Void>(VERSION_WORKSPACE) {
125
126 @Override
127 public Void exec(Session session) throws RepositoryException {
128 try {
129 Node tmp = session.getNode("/" + VersionManager.TMP_REFERENCED_NODES);
130
131 NodeIterator children = tmp.getNodes();
132 while (children.hasNext()) {
133 Node child = children.nextNode();
134 if (child.getReferences().getSize() < 1) {
135 child.remove();
136 }
137 }
138 }
139 catch (PathNotFoundException e) {
140 session.getRootNode().addNode(VersionManager.TMP_REFERENCED_NODES, ItemType.SYSTEM.getSystemName());
141 }
142 session.save();
143
144 return null;
145 }
146 });
147
148 }
149
150
151
152
153
154
155
156
157 public synchronized Version addVersion(Node node) throws UnsupportedRepositoryOperationException,
158 RepositoryException {
159
160 Rule rule = new Rule(VersionUtil.getNodeTypeName(node) + "," + ItemType.SYSTEM.getSystemName(), ",");
161 rule.reverse();
162 return this.addVersion(node, rule);
163 }
164
165
166
167
168
169
170
171
172 public synchronized Version addVersion(final Node node, final Rule rule) throws UnsupportedRepositoryOperationException,
173 RepositoryException {
174 final String userName = getSafelyUsersNameFromMgnlContenxt();
175 Version version = MgnlContext.doInSystemContext(new JCRSessionOp<Version>(node.getSession().getWorkspace().getName()) {
176
177 @Override
178 public Version exec(Session session) throws RepositoryException {
179 try {
180 return createVersion(session.getNodeByIdentifier(node.getIdentifier()), rule, userName);
181 }
182 catch (RepositoryException re) {
183
184
185 log.error("failed to copy versionable node to version store, reverting all changes made in this session");
186 getSession().refresh(false);
187 throw re;
188 }
189
190 }});
191 return version;
192 }
193
194
195
196
197
198
199
200
201
202
203 @Deprecated
204 protected Version createVersion(Content node, Rule rule) throws UnsupportedRepositoryOperationException, RepositoryException {
205 return createVersion(node.getJCRNode(), rule, getSafelyUsersNameFromMgnlContenxt());
206 }
207
208 private String getSafelyUsersNameFromMgnlContenxt(){
209 String userName = "";
210 if (MgnlContext.getUser() != null) {
211 userName = MgnlContext.getUser().getName();
212 }
213 return userName;
214 }
215
216
217
218
219
220
221
222
223
224
225 protected Version createVersion(Node node, Rule rule, String userName) throws UnsupportedRepositoryOperationException,
226 RepositoryException {
227 if (isInvalidMaxVersions()) {
228 log.debug("Ignore create version, MaxVersionIndex < 1");
229 log.debug("Returning root version of the source node");
230 return node.getVersionHistory().getRootVersion();
231 }
232
233 CopyUtil.getInstance().copyToversion(node, new RuleBasedNodePredicate(rule));
234 Node versionedNode = this.getVersionedNode(node);
235
236 checkAndAddMixin(versionedNode);
237 Node systemInfo = this.getSystemNode(versionedNode);
238
239 ByteArrayOutputStream out = new ByteArrayOutputStream();
240 try {
241 ObjectOutput objectOut = new ObjectOutputStream(out);
242 objectOut.writeObject(rule);
243 objectOut.flush();
244 objectOut.close();
245
246 systemInfo.setProperty(PROPERTY_RULE, new String(Base64.encodeBase64(out.toByteArray())));
247 }
248 catch (IOException e) {
249 throw new RepositoryException("Unable to add serialized Rule to the versioned content");
250 }
251
252 systemInfo.setProperty(ContentVersion.VERSION_USER, userName);
253 systemInfo.setProperty(ContentVersion.NAME, node.getName());
254
255 versionedNode.save();
256
257 Version newVersion = versionedNode.checkin();
258 versionedNode.checkout();
259
260 try {
261 this.setMaxVersionHistory(versionedNode);
262 }
263 catch (RepositoryException re) {
264 log.error("Failed to limit version history to the maximum configured", re);
265 log.error("New version has already been created");
266 }
267
268 return newVersion;
269 }
270
271
272
273
274 public abstract boolean isInvalidMaxVersions();
275
276
277
278
279 public synchronized Node getVersionedNode(Node node) throws RepositoryException {
280 return getVersionedNode(node.getIdentifier());
281 }
282
283
284
285
286 protected Node getVersionedNode(String uuid) throws RepositoryException {
287 try {
288 return getSession().getNodeByIdentifier(uuid);
289 } catch (ItemNotFoundException e) {
290
291 return null;
292 }
293 }
294
295
296
297
298
299 public abstract void setMaxVersionHistory(Node node) throws RepositoryException;
300
301
302
303
304
305
306
307
308 public synchronized VersionHistory getVersionHistory(Node node) throws UnsupportedRepositoryOperationException,
309 RepositoryException {
310 try {
311 Node versionedNode = this.getVersionedNode(node);
312 if (versionedNode == null) {
313
314 log.debug("No VersionHistory found for {} node.", node);
315 return null;
316 }
317 return versionedNode.getVersionHistory();
318 } catch (UnsupportedRepositoryOperationException e) {
319 log.debug("Node {} is not versionable.", node);
320
321 return null;
322 }
323 }
324
325
326
327
328
329
330
331
332 public synchronized Version getVersion(Node node, String name) throws UnsupportedRepositoryOperationException,
333 RepositoryException {
334 VersionHistory history = this.getVersionHistory(node);
335 if (history != null) {
336 return new VersionedNode(history.getVersion(name), node);
337 }
338 log.error("Node " + node.getPath() + " was never versioned");
339 return null;
340 }
341
342
343
344
345
346
347 public Version getBaseVersion(Node node) throws UnsupportedOperationException, RepositoryException {
348 Node versionedNode = this.getVersionedNode(node);
349 if (versionedNode != null) {
350 return versionedNode.getBaseVersion();
351 }
352
353 throw new RepositoryException("Node " + node.getPath() + " was never versioned");
354 }
355
356
357
358
359
360
361
362
363 public synchronized VersionIterator getAllVersions(Node node) throws UnsupportedRepositoryOperationException, RepositoryException {
364 Node versionedNode = this.getVersionedNode(node);
365 if (versionedNode == null) {
366
367 return null;
368 }
369 return versionedNode.getVersionHistory().getAllVersions();
370 }
371
372
373
374
375
376
377
378
379
380
381
382
383 @Deprecated
384 public synchronized void restore(Content node, Version version, boolean removeExisting) throws VersionException, UnsupportedRepositoryOperationException, RepositoryException {
385 restore(node.getJCRNode(), version, removeExisting);
386 }
387
388
389
390
391
392
393
394
395
396
397 public synchronized void restore(final Node node, Version version, boolean removeExisting) throws VersionException, UnsupportedRepositoryOperationException, RepositoryException {
398
399 PermissionUtil.verifyIsGrantedOrThrowException(node.getSession(), node.getPath(), Session.ACTION_SET_PROPERTY + "," + Session.ACTION_REMOVE + "," + Session.ACTION_ADD_NODE);
400
401
402 final Node versionedNode = this.getVersionedNode(node);
403
404 final Version unwrappedVersion;
405 if (version instanceof VersionedNode) {
406 unwrappedVersion = ((VersionedNode) version).unwrap();
407 } else {
408 unwrappedVersion = version;
409 }
410
411 versionedNode.restore(unwrappedVersion, removeExisting);
412 versionedNode.checkout();
413
414 final String user = MgnlContext.getUser().getName();
415 MgnlContext.doInSystemContext(new JCRSessionOp<Void>(versionedNode.getSession().getWorkspace().getName()) {
416
417 @Override
418 public Void exec(Session session) throws RepositoryException {
419
420 List<String> mixins = new ArrayList<String>();
421 for (Value v: unwrappedVersion.getNode(ItemType.JCR_FROZENNODE).getProperty("jcr:frozenMixinTypes").getValues()) {
422 mixins.add(v.getString());
423 }
424
425 final Node systemVersionedNode = session.getNodeByIdentifier(versionedNode.getUUID());
426 for (NodeType nt : versionedNode.getMixinNodeTypes()) {
427 if (!mixins.remove(nt.getName())) {
428 systemVersionedNode.removeMixin(nt.getName());
429 }
430 }
431 for (String mix : mixins) {
432 systemVersionedNode.addMixin(mix);
433 }
434 systemVersionedNode.save();
435
436 try {
437
438 Session sysDestinationSession = MgnlContext.getJCRSession(node.getSession().getWorkspace().getName());
439 log.debug("restoring info:{}:{}", sysDestinationSession.getWorkspace().getName(), node.getPath());
440 Node destinationNode = sysDestinationSession.getNode(node.getPath());
441
442 Rule rule = getUsedFilter(versionedNode);
443 try {
444 synchronized (ExclusiveWrite.getInstance()) {
445 RuleBasedNodePredicate ruleBasedNodePredicate = new RuleBasedNodePredicate(rule);
446 CopyUtil.getInstance().copyFromVersion(versionedNode, destinationNode, ruleBasedNodePredicate);
447 if (NodeUtil.hasMixin(destinationNode, ItemType.DELETED_NODE_MIXIN)) {
448 destinationNode.removeMixin(ItemType.DELETED_NODE_MIXIN);
449 }
450 updateModificationDate(destinationNode, ruleBasedNodePredicate, user);
451 destinationNode.save();
452 }
453
454 node.refresh(false);
455 }
456 catch (RepositoryException re) {
457 if (log.isDebugEnabled()) {
458 log.debug("error during restore: " + re.getMessage(), re);
459 } else {
460 log.error("failed to restore versioned node, reverting all changes make to this node");
461 }
462 destinationNode.refresh(false);
463 throw re;
464 }
465 }
466 catch (IOException e) {
467 throw new RepositoryException(e);
468 }
469 catch (ClassNotFoundException e) {
470 throw new RepositoryException(e);
471 }
472 return null;
473 }
474 });
475 }
476
477
478
479
480
481 public synchronized void removeVersionHistory(final Node node) throws RepositoryException {
482 PermissionUtil.verifyIsGrantedOrThrowException(node.getSession(), Path.getAbsolutePath(node.getPath()), Session.ACTION_ADD_NODE + "," + Session.ACTION_REMOVE + "," + Session.ACTION_SET_PROPERTY);
483 final String uuid = node.getIdentifier();
484 MgnlContext.doInSystemContext(new JCRSessionOp<Void>(VersionManager.VERSION_WORKSPACE) {
485
486 @Override
487 public Void exec(Session session) throws RepositoryException {
488 Node node = getVersionedNode(uuid);
489 if (node != null) {
490 if (node.getReferences().getSize() < 1) {
491
492 node.remove();
493 } else {
494 VersionHistory history = node.getVersionHistory();
495 VersionIterator versions = history.getAllVersions();
496 if (versions != null) {
497
498 versions.nextVersion();
499 while (versions.hasNext()) {
500 history.removeVersion(versions.nextVersion().getName());
501 }
502 }
503 }
504 }
505 session.save();
506 return null;
507 }
508 });
509 }
510
511 private void updateModificationDate(Node node, RuleBasedNodePredicate ruleBasedNodePredicate, String user) throws RepositoryException {
512 MetaDataUtil.updateMetaData(node, user);
513 Iterator<Node> childNodes = NodeUtil.collectAllChildren(node, ruleBasedNodePredicate).iterator();
514 while(childNodes.hasNext()){
515 Node childNode = childNodes.next();
516 if(!childNode.isNodeType(MgnlNodeType.NT_METADATA)){
517 MetaDataUtil.updateMetaData(childNode, user);
518 }
519 }
520 }
521
522
523
524
525
526 protected void checkAndAddMixin(Node node) throws RepositoryException {
527 if(!node.isNodeType("mix:versionable")){
528 log.debug("Add mix:versionable");
529 node.addMixin("mix:versionable");
530 }
531 }
532
533
534
535
536
537
538
539
540 protected Rule getUsedFilter(Node versionedNode) throws IOException, ClassNotFoundException, RepositoryException {
541
542 ByteArrayInputStream inStream = null;
543 try {
544 String ruleString = this.getSystemNode(versionedNode).getProperty(PROPERTY_RULE).getString();
545 inStream = new ByteArrayInputStream(Base64.decodeBase64(ruleString.getBytes()));
546 ObjectInput objectInput = new ObjectInputStream(inStream);
547 return (Rule) objectInput.readObject();
548 }
549 catch (IOException e) {
550 throw e;
551 }
552 catch (ClassNotFoundException e) {
553 throw e;
554 }
555 finally {
556 IOUtils.closeQuietly(inStream);
557 }
558 }
559
560
561
562
563
564
565 protected synchronized Node getSystemNode(Node node) throws RepositoryException {
566 if (node.hasNode(SYSTEM_NODE)) {
567 return node.getNode(SYSTEM_NODE);
568 }
569 return node.addNode(SYSTEM_NODE, ItemType.SYSTEM.getSystemName());
570 }
571
572
573
574
575
576
577 protected Session getSession() throws LoginException, RepositoryException {
578 return MgnlContext.getSystemContext().getJCRSession(VersionManager.VERSION_WORKSPACE);
579 }
580 }