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