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