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.FileSystemHelper;
37 import info.magnolia.context.SystemContext;
38 import info.magnolia.jcr.util.NodeTypes;
39 import info.magnolia.jcr.util.NodeUtil;
40 import info.magnolia.jcr.util.VersionUtil;
41 import info.magnolia.jcr.wrapper.DelegateSessionWrapper;
42 import info.magnolia.jcr.wrapper.MgnlPropertySettingContentDecorator;
43 import info.magnolia.objectfactory.Components;
44 import info.magnolia.repository.RepositoryConstants;
45 import info.magnolia.repository.RepositoryManager;
46
47 import java.io.File;
48 import java.io.FileInputStream;
49 import java.io.FileOutputStream;
50 import java.io.IOException;
51 import java.util.ArrayList;
52 import java.util.List;
53
54 import javax.inject.Inject;
55 import javax.inject.Singleton;
56 import javax.jcr.ImportUUIDBehavior;
57 import javax.jcr.ItemNotFoundException;
58 import javax.jcr.Node;
59 import javax.jcr.NodeIterator;
60 import javax.jcr.Property;
61 import javax.jcr.PropertyIterator;
62 import javax.jcr.PropertyType;
63 import javax.jcr.RepositoryException;
64 import javax.jcr.Session;
65 import javax.jcr.Workspace;
66 import javax.jcr.nodetype.ConstraintViolationException;
67 import javax.jcr.nodetype.NodeType;
68
69 import org.apache.commons.io.IOUtils;
70 import org.apache.commons.lang3.RandomStringUtils;
71 import org.apache.commons.lang3.StringUtils;
72 import org.apache.jackrabbit.JcrConstants;
73 import org.apache.jackrabbit.commons.iterator.FilteringNodeIterator;
74 import org.apache.jackrabbit.commons.predicate.Predicate;
75 import org.slf4j.Logger;
76 import org.slf4j.LoggerFactory;
77
78
79
80
81
82
83
84
85 @Singleton
86 public final class CopyUtil {
87
88 private static Logger log = LoggerFactory.getLogger(CopyUtil.class);
89
90 private final SystemContext systemContext;
91 private final RepositoryManager repositoryManager;
92 private final FileSystemHelper fileSystemHelper;
93
94 @Inject
95 public CopyUtil(SystemContext systemContext, RepositoryManager repositoryManager, FileSystemHelper fileSystemHelper) {
96 this.systemContext = systemContext;
97 this.repositoryManager = repositoryManager;
98 this.fileSystemHelper = fileSystemHelper;
99 }
100
101
102
103
104 @Deprecated
105 public CopyUtil(SystemContext systemContext, RepositoryManager repositoryManager) {
106 this(systemContext, repositoryManager, Components.getComponent(FileSystemHelper.class));
107 }
108
109
110
111
112 @Deprecated
113 void copyToversion(Node source, Predicate filter) throws RepositoryException {
114 copyToVersion(source, filter);
115 }
116
117
118
119
120 void copyToVersion(Node source, Predicate filter) throws RepositoryException {
121
122 Node root;
123 try {
124 root = getVersionSessionFor(source).getNodeByIdentifier(source.getUUID());
125 if (root.getParent().getName().equalsIgnoreCase(VersionManager.TMP_REFERENCED_NODES)) {
126 root.getSession().move(root.getPath(), "/" + root.getName());
127 } else if (root.getDepth() > 3) {
128 this.removeProperties(root);
129
130 this.updateProperties(source, root);
131
132 this.updateNodeTypes(source, root);
133 saveSessionWithoutPropertySessionWrapper(root.getSession());
134 } else {
135 root = doImport(source);
136 }
137 } catch (ItemNotFoundException e) {
138
139 root = doImport(source);
140 }
141
142 NodeIterator children = new FilteringNodeIterator(source.getNodes(), filter);
143 while (children.hasNext()) {
144 Node child = children.nextNode();
145 this.clone(child, root, filter, true);
146 }
147 this.removeNonExistingChildNodes(source, root, filter);
148 }
149
150 private Node doImport(Node source) throws RepositoryException {
151 try {
152 this.importNode(getVersionSessionFor(source).getRootNode(), source);
153 } catch (IOException ioe) {
154 throw new RepositoryException("Failed to import node in magnolia version store : " + ioe.getMessage(), ioe);
155 }
156 Node root = getVersionSessionFor(source).getNodeByIdentifier(source.getIdentifier());
157
158 saveSessionWithoutPropertySessionWrapper(root.getSession());
159 return root;
160 }
161
162 private void updateNodeTypes(Node source, Node root) throws RepositoryException {
163 String sourcePrimaryNodeType = source.getPrimaryNodeType().getName();
164 if (!root.getPrimaryNodeType().getName().equals(sourcePrimaryNodeType)) {
165 root.setPrimaryType(sourcePrimaryNodeType);
166 }
167
168 List<String> targetNodeTypes = new ArrayList<String>();
169 for (NodeType t : root.getMixinNodeTypes()) {
170 targetNodeTypes.add(t.getName());
171 }
172 NodeType[] nodeTypes = source.getMixinNodeTypes();
173 for (NodeType type : nodeTypes) {
174 root.addMixin(type.getName());
175 targetNodeTypes.remove(type.getName());
176 }
177
178 for (String nodeType : targetNodeTypes) {
179 if (JcrConstants.MIX_VERSIONABLE.equals(nodeType)) {
180 continue;
181 }
182 root.removeMixin(nodeType);
183 }
184 }
185
186
187
188
189
190
191
192
193 void copyFromVersion(Node source, Node destination, Predicate filter) throws RepositoryException {
194
195 this.copyAllChildNodes(source, destination, filter);
196
197 this.removeNonExistingChildNodes(source, destination, filter);
198
199
200 this.removeProperties(destination);
201 this.updateProperties(source, destination);
202
203 this.removeNonExistingMixins(source, destination);
204 }
205
206 private void removeNonExistingMixins(Node source, Node destination) throws RepositoryException {
207 List<String> destNodeTypes = new ArrayList<String>();
208
209 for (NodeType nt : destination.getMixinNodeTypes()) {
210 destNodeTypes.add(nt.getName());
211 }
212
213 for (NodeType nt : source.getMixinNodeTypes()) {
214 destNodeTypes.remove(nt.getName());
215 }
216
217 for (String type : destNodeTypes) {
218 destination.removeMixin(type);
219 }
220 }
221
222
223
224
225 private void removeNonExistingChildNodes(Node source, Node destination, Predicate filter)
226 throws RepositoryException {
227
228 NodeIterator children = new FilteringNodeIterator(destination.getNodes(), filter);
229 while (children.hasNext()) {
230 Node child = children.nextNode();
231
232 if (child.getDefinition().isAutoCreated()) {
233 continue;
234 }
235 try {
236 source.getSession().getNodeByIdentifier(child.getIdentifier());
237
238 this.removeNonExistingChildNodes(source, child, filter);
239 } catch (ItemNotFoundException e) {
240 PropertyIterator referencedProperties = child.getReferences();
241 if (referencedProperties.getSize() > 0) {
242
243
244 while (referencedProperties.hasNext()) {
245 referencedProperties.nextProperty().remove();
246 }
247 }
248 child.remove();
249 }
250 }
251 }
252
253
254
255
256 private void copyAllChildNodes(Node node1, Node node2, Predicate filter)
257 throws RepositoryException {
258 NodeIterator children = new FilteringNodeIterator(node1.getNodes(), filter);
259 while (children.hasNext()) {
260 Node child = children.nextNode();
261 this.clone(child, node2, filter, true);
262 }
263 }
264
265 public void clone(Node node, Node parent, Predicate filter, boolean removeExisting)
266 throws RepositoryException {
267 try {
268
269
270
271 String workspaceName = parent.getSession().getWorkspace().getName();
272 Session session = getSession(workspaceName);
273 Node existingNode = session.getNodeByIdentifier(node.getIdentifier());
274 if (removeExisting) {
275 existingNode.remove();
276 saveSessionWithoutPropertySessionWrapper(session);
277 this.clone(node, parent);
278 return;
279 }
280 this.removeProperties(existingNode);
281 this.updateProperties(node, existingNode);
282 NodeIterator children = new FilteringNodeIterator(node.getNodes(), filter);
283 while (children.hasNext()) {
284 this.clone(children.nextNode(), existingNode, filter, removeExisting);
285 }
286 } catch (ItemNotFoundException e) {
287
288 if (parent.hasNode(node.getName())) {
289 parent.getNode(node.getName()).remove();
290 parent.getSession().save();
291 }
292 this.clone(node, parent);
293 }
294 }
295
296 private void clone(Node node, Node parent) throws RepositoryException {
297 if (node.getDefinition().isAutoCreated()) {
298 Node destination = parent.getNode(node.getName());
299 this.removeProperties(destination);
300 this.updateProperties(node, destination);
301 } else {
302 final String parentPath = parent.getPath();
303 final String srcWorkspaceLogicalName = node.getSession().getWorkspace().getName();
304 final String srcWorkspacePhysicalName = repositoryManager.getWorkspaceMapping(srcWorkspaceLogicalName).getPhysicalWorkspaceName();
305 final Workspace targetWorkspace = parent.getSession().getWorkspace();
306 final String srcPath = node.getPath();
307 final String targetPath = parentPath + (parentPath != null && parentPath.endsWith("/") ? "" : "/") + node.getName();
308 log.debug("workspace level clone from {}:{} to {}:{}", srcWorkspaceLogicalName, srcPath, targetWorkspace.getName(), parentPath);
309 targetWorkspace.clone(srcWorkspacePhysicalName, srcPath, targetPath, true);
310 }
311 }
312
313
314
315
316 private void removeProperties(Node node) throws RepositoryException {
317 PropertyIterator properties = node.getProperties();
318 while (properties.hasNext()) {
319 Property property = properties.nextProperty();
320 if (property.getDefinition().isProtected() || property.getDefinition().isMandatory()) {
321 continue;
322 }
323 try {
324 property.remove();
325 } catch (ConstraintViolationException e) {
326 log.debug("Property {} is a reserved property", property.getName());
327 }
328 }
329 }
330
331
332
333
334
335
336
337 private void importNode(Node parent, Node node) throws RepositoryException, IOException {
338 File file = File.createTempFile("mgnl", null, fileSystemHelper.getTempDirectory());
339 FileOutputStream outStream = new FileOutputStream(file);
340 try {
341 node.getSession().getWorkspace().getSession().exportSystemView(node.getPath(), outStream, false, true);
342 outStream.flush();
343 } finally {
344 IOUtils.closeQuietly(outStream);
345 }
346 FileInputStream inStream = new FileInputStream(file);
347 String tempPath = null;
348 Node maybeVersionNode;
349 try {
350
351
352
353
354 maybeVersionNode = getVersionSessionFor(node).getNodeByIdentifier(node.getIdentifier());
355
356
357 if (maybeVersionNode.getDepth() < 4) {
358
359 Iterable<Node> children = NodeUtil.collectAllChildren(maybeVersionNode, object -> {
360 Node n = (Node) object;
361 try {
362 if (n.getDepth() == 4) {
363 return true;
364 }
365 } catch (RepositoryException e) {
366 log.debug("Unable to get node depth", e);
367 return false;
368 }
369 return false;
370 });
371 tempPath = RandomStringUtils.randomAlphabetic(16);
372 Node tempNode = parent.addNode(tempPath);
373 for (Node child : children) {
374 NodeUtil.moveNode(child, tempNode);
375 }
376 maybeVersionNode.remove();
377 }
378 } catch (ItemNotFoundException e) {
379
380 }
381
382 try {
383 parent.getSession().getWorkspace().getSession().importXML(
384 NodeUtil.createPath(parent, getSavePath(node), NodeTypes.Folder.NAME, true).getPath(),
385 inStream,
386 ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING);
387
388 NodeUtil.renameNode(parent.getSession().getNodeByIdentifier(node.getIdentifier()), node.getIdentifier());
389 } finally {
390 IOUtils.closeQuietly(inStream);
391 }
392
393 if (StringUtils.isNotBlank(tempPath)) {
394 moveChildren(parent, parent.getNode(tempPath));
395
396 parent.getNode(tempPath).remove();
397 parent.getSession().save();
398 }
399 file.delete();
400 }
401
402 private void moveChildren(Node parent, Node tempNode) throws RepositoryException {
403 for (Node child : NodeUtil.getNodes(tempNode)) {
404 Node newParent = NodeUtil.createPath(parent, getSavePath(child), NodeTypes.Folder.NAME, false);
405 NodeUtil.moveNode(child, newParent);
406 }
407 }
408
409
410
411
412 private void updateProperties(Node source, Node destination) throws RepositoryException {
413 PropertyIterator properties = source.getProperties();
414 while (properties.hasNext()) {
415 Property property = properties.nextProperty();
416
417 String propertyName = property.getName();
418 if (propertyName.equalsIgnoreCase(VersionManager.PROPERTY_RULE)) {
419 continue;
420 }
421 try {
422 if (property.getDefinition().isProtected()) {
423 continue;
424 }
425 if ("jcr:isCheckedOut".equals(propertyName)) {
426
427
428
429
430 continue;
431 }
432 if (property.getType() == PropertyType.REFERENCE) {
433
434 String destWorkspaceName = destination.getSession().getWorkspace().getName();
435 try {
436 getSession(destWorkspaceName).getNodeByIdentifier(property.getString());
437 } catch (ItemNotFoundException e) {
438 String repositoryId = repositoryManager.getRepositoryNameForWorkspace(destWorkspaceName);
439 if (!StringUtils.equalsIgnoreCase(destWorkspaceName, repositoryId + "-" + RepositoryConstants.VERSION_STORE)) {
440 throw e;
441 }
442
443
444 String srcWorkspaceName = source.getSession().getWorkspace().getName();
445 Node referencedNode = getSession(srcWorkspaceName).getNodeByIdentifier(property.getString());
446 try {
447 this.importNode(getTemporaryPath(source), referencedNode);
448 this.removeProperties(getVersionSessionFor(source).getNodeByIdentifier(property.getString()));
449 getTemporaryPath(source).save();
450 } catch (IOException ioe) {
451 log.error("Failed to import referenced node", ioe);
452 }
453 }
454 }
455 if (property.getDefinition().isMultiple()) {
456 destination.setProperty(propertyName, property.getValues());
457 } else {
458 destination.setProperty(propertyName, property.getValue());
459 }
460 } catch (ConstraintViolationException e) {
461 log.debug("Property {} is a reserved property", propertyName);
462 }
463 }
464 }
465
466
467
468
469 private Session getSession(String workspaceId) throws RepositoryException {
470 return systemContext.getJCRSession(workspaceId);
471 }
472
473 private Session getVersionSessionFor(Node source) throws RepositoryException {
474 final String versionWorkspace = VersionUtil.getVersionWorkspaceForNode(repositoryManager, source);
475 return systemContext.getJCRSession(versionWorkspace);
476 }
477
478
479
480
481 private Node getTemporaryPath(Node node) throws RepositoryException {
482 return getVersionSessionFor(node).getNode("/" + VersionManager.TMP_REFERENCED_NODES);
483 }
484
485
486
487
488 private void saveSessionWithoutPropertySessionWrapper(Session session) throws RepositoryException {
489 if (session instanceof DelegateSessionWrapper) {
490 session = ((DelegateSessionWrapper) session).deepUnwrap(MgnlPropertySettingContentDecorator.MgnlPropertySettingSessionWrapper.class);
491 }
492 session.save();
493 }
494
495 static String getSavePath(Node node) throws RepositoryException {
496 String uuid = node.getIdentifier();
497 return String.format("%s/%s/%s", StringUtils.substring(uuid, 0, 2), StringUtils.substring(uuid, 9, 11), StringUtils.substring(uuid, 14, 16));
498 }
499 }