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