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 items.clear();
96 items.addAll(source);
97 }
98 }
99
100 @Override
101 public List<Item> paste(final Item destination) {
102 List<Item> pastedItems = new ArrayList<>();
103 try {
104 if (canPasteInto(destination)) {
105 for (Item sourceItem : items) {
106 pastedItems.add(pasteSingleItem(sourceItem, (Node) destination));
107 }
108 }
109 return pastedItems;
110 } catch (RepositoryException e) {
111 throw new ContentClipboardException("Failed while pasting items under JCR item.", e);
112 }
113 }
114
115 @Override
116 public List<Item> pasteToRoot() {
117 return paste(this.jcrDatasource.getRoot());
118 }
119
120 @Override
121 public boolean canCopy(final List<Item> source) {
122 if (source.isEmpty()) {
123 return false;
124 } else {
125 for (Item item : source) {
126 if (item.isNode() && (Exceptions.wrap().get(() -> NodeUtil.hasMixin((Node) item, NodeTypes.Deleted.NAME)))) {
127 return false;
128 }
129 }
130 return true;
131 }
132 }
133
134 @Override
135 public boolean canPasteInto() {
136 return canPasteInto(this.jcrDatasource.getRoot());
137 }
138
139 @Override
140 public boolean canPasteInto(final Item destination) {
141 if (!destination.isNode() || items.isEmpty()) {
142 return false;
143 }
144 return items.stream().allMatch(item -> canPasteInto(item, destination));
145 }
146
147 private Item pasteSingleItem(final Item sourceItem, final Node destinationNode) throws RepositoryException {
148 if (sourceItem.isNode()) {
149 return pasteSingleNode((Node) sourceItem, destinationNode);
150 } else {
151 return pasteSingleProperty((Property) sourceItem, destinationNode);
152 }
153 }
154
155 private Item pasteSingleNode(final Node sourceNode, final Node destinationNode) throws RepositoryException {
156
157
158
159 String newName = getUniqueNewItemName(sourceNode, destinationNode);
160 String newPath = NodeUtil.getAbsolutePath(destinationNode.getPath(), newName);
161
162 Workspace workspace = sourceNode.getSession().getWorkspace();
163 if (isCut) {
164 workspace.move(sourceNode.getPath(), newPath);
165 } else {
166 workspace.copy(sourceNode.getPath(), newPath);
167 }
168 sourceNode.getSession().save();
169 return sourceNode.getSession().getNode(newPath);
170 }
171
172 private Property pasteSingleProperty(final Property property, final Node destinationNode) throws RepositoryException {
173 String newName = getUniqueNewItemName(property, destinationNode);
174 if (property.isMultiple()) {
175 destinationNode.setProperty(newName, property.getValues());
176 } else {
177 destinationNode.setProperty(newName, property.getValue());
178 }
179 if (isCut) {
180 property.remove();
181 }
182 destinationNode.getSession().save();
183 return destinationNode.getProperty(newName);
184 }
185
186
187 protected boolean canPasteInto(final Item sourceItem, final Item destinationItem) {
188 return Exceptions.wrap().get(() -> {
189 boolean isItemAvailable = itemInteractionAvailability.isItemAvailable(sourceItem);
190 if (!isItemAvailable || (isCut && sourceItem.getParent().getPath().equals(destinationItem.getPath()))) {
191 return false;
192 }
193 boolean result = false;
194 final String name = sourceItem.getName();
195 if (destinationItem.isNode() && sourceItem.getSession().getWorkspace().getName().equals(destinationItem.getSession().getWorkspace().getName())) {
196 final Node destinationNode = (Node) destinationItem;
197 final NodeType destinationNodeType = destinationNode.getPrimaryNodeType();
198 if (sourceItem.isNode()) {
199 result = destinationNodeType.canAddChildNode(name, ((Node) sourceItem).getPrimaryNodeType().getName());
200 } else {
201 Property property = (Property) sourceItem;
202 if (property.isMultiple()) {
203 result = destinationNodeType.canSetProperty(name, property.getValues());
204 } else {
205 result = destinationNodeType.canSetProperty(name, property.getValue());
206 }
207 }
208 }
209 return result;
210 });
211 }
212
213
214
215
216
217 private String getUniqueNewItemName(Item referenceItem, Node destination) throws RepositoryException {
218 return nodeNameHelper.getUniqueName(destination.getSession(), destination.getPath(), referenceItem.getName());
219 }
220
221 protected void setItems(List<Item> items) {
222 this.items = items;
223 }
224 }