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