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.editor;
35
36 import static java.util.stream.Collectors.*;
37
38 import info.magnolia.jcr.util.NodeNameHelper;
39 import info.magnolia.jcr.util.NodeTypes;
40 import info.magnolia.jcr.util.NodeUtil;
41 import info.magnolia.jcr.util.PropertyUtil;
42 import info.magnolia.objectfactory.Components;
43 import info.magnolia.ui.editor.JcrItemPropertySet.JcrPropertyDescriptor;
44 import info.magnolia.util.CollectionConversionCapableBeanUtils;
45
46 import java.io.File;
47 import java.io.IOException;
48 import java.util.ArrayList;
49 import java.util.Collection;
50 import java.util.List;
51 import java.util.Objects;
52 import java.util.Optional;
53 import java.util.stream.Stream;
54
55 import javax.jcr.Item;
56 import javax.jcr.Node;
57 import javax.jcr.Property;
58 import javax.jcr.PropertyType;
59 import javax.jcr.RepositoryException;
60
61 import org.apache.commons.beanutils.BeanUtilsBean;
62 import org.apache.commons.beanutils.ConversionException;
63 import org.apache.commons.lang3.StringUtils;
64
65 import com.machinezoo.noexception.Exceptions;
66
67
68
69
70
71
72
73
74 abstract class JcrItemInteractionStrategyImpl<I extends Item> implements JcrItemInteractionStrategy<I>, JcrBinaryHelper {
75
76 private static final String JCR_NAME = "jcrName";
77 private final BeanUtilsBean beanUtils = new CollectionConversionCapableBeanUtils();
78
79 <V> V getPropertyValue(Property property, Class<V> type) {
80 if (!Exceptions.wrap().get(property::isMultiple)) {
81 return convertToPresentation(PropertyUtil.getValueObject(Exceptions.wrap().get(property::getValue)), type).orElse(null);
82 } else {
83
84
85 return (V) beanUtils.getConvertUtils().convert(Stream.of(Exceptions.wrap().get(property::getValues))
86 .map(PropertyUtil::getValueObject)
87 .collect(toSet()),
88 type);
89 }
90 }
91
92 <V> void setPropertyValue(Node node, String name, V value) throws RepositoryException, IOException {
93 if (value instanceof File) {
94 createBinary(node, (File) value);
95 } else if (node.hasProperty(name) && propertyValueEquals(node.getProperty(name), value)) {
96
97 } else {
98 PropertyUtil.setProperty(node, name, value);
99 }
100 }
101
102 private <V> boolean propertyValueEquals(Property property, V value) throws RepositoryException {
103 if (property.isMultiple()) {
104 final List<Object> values = Stream.of(property.getValues())
105 .map(PropertyUtil::getValueObject)
106 .collect(toList());
107 return value instanceof Collection && Objects.equals(values, new ArrayList<>((Collection<?>) value));
108 } else {
109 return Objects.equals(PropertyUtil.getValueObject(property.getValue()), value);
110 }
111 }
112
113 protected <V> Optional<V> convertToPresentation(Object value, Class<V> targetType) {
114 try {
115
116 return Optional.ofNullable((V) beanUtils.getConvertUtils().convert(value, targetType));
117 } catch (ConversionException e) {
118 return Optional.empty();
119 }
120 }
121
122
123
124
125 final static class WithNodes extends JcrItemInteractionStrategyImpl<Node> {
126
127 @Override
128 public <V> V get(Node node, JcrPropertyDescriptor<V> descriptor) {
129 Property property;
130 if (File.class.equals(descriptor.getType())) {
131 return (V) Exceptions.wrap().get(() -> getBinary(node));
132 } else {
133 property = PropertyUtil.getPropertyOrNull(node, JCR_NAME.equals(descriptor.getName()) ?
134 Optional.ofNullable(descriptor.getNodeNameProperty()).orElse(JCR_NAME) :
135 descriptor.getName()
136 );
137 }
138 if (shouldReturnRealNodeName(descriptor, property) && !node.isNew()) {
139
140 return (V) NodeUtil.getName(node);
141 }
142 return Optional.ofNullable(property)
143 .map(prop -> getPropertyValue(prop, descriptor.getType())).orElse(null);
144 }
145
146 private boolean shouldReturnRealNodeName(JcrPropertyDescriptor<?> descriptor, Property property) {
147 return isNodeNameProperty(descriptor) && (property == null || descriptor.getNodeNameProperty() == null);
148 }
149
150 @Override
151 public <V> void set(Node node, V value, JcrPropertyDescriptor<V> descriptor) {
152 Optional<Property> property = Optional.ofNullable(PropertyUtil.getPropertyOrNull(node, descriptor.getName()));
153 if (notNullOrEmptyString(value)) {
154
155
156
157 if (!descriptor.isReadonly() || !property.isPresent()) {
158 Exceptions.wrap().run(() -> {
159 if ((isNodeNameProperty(descriptor))) {
160 handleJcrItemNameChange(node, descriptor.getNodeNameProperty(), String.valueOf(value));
161 } else {
162 setPropertyValue(node, descriptor.getName(), value);
163 }
164 });
165 }
166 } else {
167
168 property.ifPresent(Exceptions.wrap().consumer(prop -> {
169 if (!prop.getName().startsWith(NodeTypes.JCR_PREFIX)) {
170 prop.remove();
171 }
172 }));
173 }
174 }
175
176 private boolean notNullOrEmptyString(Object value) {
177 if (value instanceof String) {
178 return StringUtils.isNotEmpty((String) value);
179 }
180 return value != null;
181 }
182 }
183
184 private static boolean isNodeNameProperty(JcrPropertyDescriptor<?> descriptor) {
185 return JCR_NAME.equals(descriptor.getName()) || descriptor.getName().equals(descriptor.getNodeNameProperty());
186 }
187
188
189
190
191 final static class WithProperties extends JcrItemInteractionStrategyImpl<Property> {
192
193 @Override
194 public <V> V get(Property property, JcrPropertyDescriptor<V> descriptor) {
195 if (isNodeNameProperty(descriptor)) {
196
197 return (V) Exceptions.wrap().get(property::getName);
198 }
199 switch (descriptor.getName()) {
200 case "value":
201 return getPropertyValue(property, descriptor.getType());
202
203
204
205
206 case "jcrType":
207 return (V) PropertyType.nameFromValue(Exceptions.wrap().get(property::getType));
208 default:
209 return null;
210 }
211 }
212
213 @Override
214 public <V> void set(Property item, V value, JcrPropertyDescriptor<V> descriptor) {
215 Exceptions.wrap().run(() -> {
216 if (isNodeNameProperty(descriptor)) {
217
218 handleJcrItemNameChange(item, descriptor.getNodeNameProperty(), String.valueOf(value));
219 } else if ("value".equals(descriptor.getName())) {
220 item.setValue(PropertyUtil.createValue(value, item.getSession().getValueFactory()));
221 } else {
222 throw new IllegalArgumentException("Supports setting only value, jcrName or configured node name property: " + descriptor.getName());
223 }
224 });
225 }
226 }
227
228 private static final NodeNameHelper nodeNameHelper = Components.getComponent(NodeNameHelper.class);
229
230 private static void handleJcrItemNameChange(Item jcrItem, String propertyName, String pendingNewName) throws RepositoryException {
231 if (StringUtils.isNotEmpty(pendingNewName)) {
232 final String validatedName = nodeNameHelper.getValidatedName(pendingNewName);
233 if (jcrItem.isNode()) {
234 final Node item = (Node) jcrItem;
235 if (!jcrItem.getName().equals(validatedName)) {
236 NodeUtil.renameNode(item, validatedName);
237 if (propertyName != null) {
238 PropertyUtil.setProperty(item, propertyName, pendingNewName);
239 }
240 }
241 } else {
242 if (!jcrItem.getName().equals(validatedName)) {
243 PropertyUtil.renameProperty((Property) jcrItem, validatedName);
244 }
245 }
246 }
247 }
248 }