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