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.ui.contentapp.action.clipboard;
35
36 import info.magnolia.jcr.util.NodeNameHelper;
37 import info.magnolia.jcr.util.NodeTypes;
38 import info.magnolia.jcr.util.NodeUtil;
39 import info.magnolia.ui.api.ioc.SubAppScoped;
40 import info.magnolia.ui.contentapp.browser.ItemInteractionAvailability;
41 import info.magnolia.ui.datasource.jcr.JcrDatasource;
42 import info.magnolia.ui.framework.ContentClipboard;
43 import info.magnolia.ui.framework.ContentClipboardException;
44
45 import java.util.ArrayList;
46 import java.util.List;
47 import java.util.stream.Stream;
48
49 import javax.inject.Inject;
50 import javax.jcr.Item;
51 import javax.jcr.Node;
52 import javax.jcr.Property;
53 import javax.jcr.RepositoryException;
54 import javax.jcr.Workspace;
55 import javax.jcr.nodetype.NodeType;
56
57 import com.machinezoo.noexception.Exceptions;
58
59
60
61
62 @SubAppScoped
63 public class JcrClipboard implements ContentClipboard<Item> {
64
65 private boolean isCut;
66 private List<Item> items = new ArrayList<>();
67
68 private final ItemInteractionAvailability<Item> itemInteractionAvailability;
69 private final NodeNameHelper nodeNameHelper;
70 private final JcrDatasource jcrDatasource;
71
72 @Inject
73 public JcrClipboard(ItemInteractionAvailability<Item> itemInteractionAvailability,
74 NodeNameHelper nodeNameHelper, JcrDatasource jcrDatasource) {
75 this.itemInteractionAvailability = itemInteractionAvailability;
76 this.nodeNameHelper = nodeNameHelper;
77 this.jcrDatasource = jcrDatasource;
78 }
79
80 @Override
81 public void cut(List<Item> source) {
82 copy(source);
83 isCut = true;
84 }
85
86 @Override
87 public Stream<Item> getContents() {
88 return this.items.stream();
89 }
90
91 @Override
92 public void copy(final List<Item> source) {
93 isCut = false;
94 if (canCopy(source)) {
95 this.items = source;
96 }
97 }
98
99 @Override
100 public List<Item> paste(final Item destination) {
101 List<Item> pastedItems = new ArrayList<>();
102 try {
103 if (canPasteInto(destination)) {
104 for (Item sourceItem : items) {
105 pastedItems.add(pasteSingleItem(sourceItem, (Node) destination));
106 }
107 }
108 return pastedItems;
109 } catch (RepositoryException e) {
110 throw new ContentClipboardException("Failed while pasting items under JCR item.", e);
111 }
112 }
113
114 @Override
115 public List<Item> pasteToRoot() {
116 return paste(this.jcrDatasource.getRoot());
117 }
118
119 @Override
120 public boolean canCopy(final List<Item> source) {
121 if (source.isEmpty()) {
122 return false;
123 } else {
124 for (Item item : source) {
125 if (item.isNode() && (Exceptions.wrap().get(() -> NodeUtil.hasMixin((Node) item, NodeTypes.Deleted.NAME)))) {
126 return false;
127 }
128 }
129 return true;
130 }
131 }
132
133 @Override
134 public boolean canPasteInto() {
135 return canPasteInto(this.jcrDatasource.getRoot());
136 }
137
138 @Override
139 public boolean canPasteInto(final Item destination) {
140 if (!destination.isNode() || items.isEmpty()) {
141 return false;
142 }
143 return items.stream().allMatch(item -> canPasteInto(item, destination));
144 }
145
146 private Item pasteSingleItem(final Item sourceItem, final Node destinationNode) throws RepositoryException {
147 if (sourceItem.isNode()) {
148 return pasteSingleNode((Node) sourceItem, destinationNode);
149 } else {
150 return pasteSingleProperty((Property) sourceItem, destinationNode);
151 }
152 }
153
154 private Item pasteSingleNode(final Node sourceNode, final Node destinationNode) throws RepositoryException {
155
156
157
158 String newName = getUniqueNewItemName(sourceNode, destinationNode);
159 String newPath = NodeUtil.getAbsolutePath(destinationNode.getPath(), newName);
160
161 Workspace workspace = sourceNode.getSession().getWorkspace();
162 if (isCut) {
163 workspace.move(sourceNode.getPath(), newPath);
164 } else {
165 workspace.copy(sourceNode.getPath(), newPath);
166 }
167 sourceNode.getSession().save();
168 return sourceNode.getSession().getNode(newPath);
169 }
170
171 private Property pasteSingleProperty(final Property property, final Node destinationNode) throws RepositoryException {
172 String newName = getUniqueNewItemName(property, destinationNode);
173 if (property.isMultiple()) {
174 destinationNode.setProperty(newName, property.getValues());
175 } else {
176 destinationNode.setProperty(newName, property.getValue());
177 }
178 if (isCut) {
179 property.remove();
180 }
181 destinationNode.getSession().save();
182 return destinationNode.getProperty(newName);
183 }
184
185
186 protected boolean canPasteInto(final Item sourceItem, final Item destinationItem) {
187 return Exceptions.wrap().get(() -> {
188 boolean isItemAvailable = itemInteractionAvailability.isItemAvailable(sourceItem);
189 if (!isItemAvailable || (isCut && sourceItem.getParent().getPath().equals(destinationItem.getPath()))) {
190 return false;
191 }
192 boolean result = false;
193 final String name = sourceItem.getName();
194 if (destinationItem.isNode() && sourceItem.getSession().getWorkspace().getName().equals(destinationItem.getSession().getWorkspace().getName())) {
195 final Node destinationNode = (Node) destinationItem;
196 final NodeType destinationNodeType = destinationNode.getPrimaryNodeType();
197 if (sourceItem.isNode()) {
198 result = destinationNodeType.canAddChildNode(name, ((Node) sourceItem).getPrimaryNodeType().getName());
199 } else {
200 Property property = (Property) sourceItem;
201 if (property.isMultiple()) {
202 result = destinationNodeType.canSetProperty(name, property.getValues());
203 } else {
204 result = destinationNodeType.canSetProperty(name, property.getValue());
205 }
206 }
207 }
208 return result;
209 });
210 }
211
212
213
214
215
216 private String getUniqueNewItemName(Item referenceItem, Node destination) throws RepositoryException {
217 return nodeNameHelper.getUniqueName(destination.getSession(), destination.getPath(), referenceItem.getName());
218 }
219 }