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