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