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