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 return (V) beanUtils.getConvertUtils().convert(Stream.of(Exceptions.wrap().get(property::getValues))
85 .map(PropertyUtil::getValueObject)
86 .collect(toList()), type);
87 }
88 }
89
90 <V> void setPropertyValue(Node node, String name, V value) throws RepositoryException, IOException {
91 if (value instanceof File) {
92 createBinary(node, (File) value);
93 } else if (node.hasProperty(name) && propertyValueEquals(node.getProperty(name), value)) {
94
95 } else {
96 PropertyUtil.setProperty(node, name, value);
97 }
98 }
99
100 private <V> boolean propertyValueEquals(Property property, V value) throws RepositoryException {
101 if (property.isMultiple()) {
102 final List<Object> values = Stream.of(property.getValues())
103 .map(PropertyUtil::getValueObject)
104 .collect(toList());
105 return value instanceof Collection && Objects.equals(values, new ArrayList<>((Collection<?>) value));
106 } else {
107 return Objects.equals(PropertyUtil.getValueObject(property.getValue()), value);
108 }
109 }
110
111 protected <V> Optional<V> convertToPresentation(Object value, Class<V> targetType) {
112 try {
113
114 return Optional.ofNullable((V) beanUtils.getConvertUtils().convert(value, targetType));
115 } catch (ConversionException e) {
116 return Optional.empty();
117 }
118 }
119
120
121
122
123 final static class WithNodes extends JcrItemInteractionStrategyImpl<Node> {
124
125 @Override
126 public <V> V get(Node node, JcrPropertyDescriptor<V> descriptor) {
127 Property property;
128 if (File.class.equals(descriptor.getType())) {
129 return (V) Exceptions.wrap().get(() -> getBinary(node));
130 } else {
131 property = PropertyUtil.getPropertyOrNull(node, JCR_NAME.equals(descriptor.getName()) ?
132 Optional.ofNullable(descriptor.getNodeNameProperty()).orElse(JCR_NAME) :
133 descriptor.getName()
134 );
135 }
136 if (shouldReturnRealNodeName(descriptor, property) && !node.isNew()) {
137
138 return (V) NodeUtil.getName(node);
139 }
140 return Optional.ofNullable(property)
141 .map(prop -> getPropertyValue(prop, descriptor.getType())).orElse(null);
142 }
143
144 private boolean shouldReturnRealNodeName(JcrPropertyDescriptor<?> descriptor, Property property) {
145 return isNodeNameProperty(descriptor) && (property == null || descriptor.getNodeNameProperty() == null);
146 }
147
148 @Override
149 public <V> void set(Node node, V value, JcrPropertyDescriptor<V> descriptor) {
150 Optional<Property> property = Optional.ofNullable(PropertyUtil.getPropertyOrNull(node, descriptor.getName()));
151 if (notNullOrEmptyString(value)) {
152
153
154
155 if (!descriptor.isReadonly() || !property.isPresent()) {
156 Exceptions.wrap().run(() -> {
157 if ((isNodeNameProperty(descriptor))) {
158 handleJcrItemNameChange(node, descriptor.getNodeNameProperty(), String.valueOf(value));
159 } else {
160 setPropertyValue(node, descriptor.getName(), value);
161 }
162 });
163 }
164 } else {
165
166 property.ifPresent(Exceptions.wrap().consumer(prop -> {
167 if (!prop.getName().startsWith(NodeTypes.JCR_PREFIX)) {
168 prop.remove();
169 }
170 }));
171 }
172 }
173
174 private boolean notNullOrEmptyString(Object value) {
175 if (value instanceof String) {
176 return StringUtils.isNotEmpty((String) value);
177 }
178 return value != null;
179 }
180 }
181
182 private static boolean isNodeNameProperty(JcrPropertyDescriptor<?> descriptor) {
183 return JCR_NAME.equals(descriptor.getName()) || descriptor.getName().equals(descriptor.getNodeNameProperty());
184 }
185
186
187
188
189 final static class WithProperties extends JcrItemInteractionStrategyImpl<Property> {
190
191 @Override
192 public <V> V get(Property property, JcrPropertyDescriptor<V> descriptor) {
193 if (isNodeNameProperty(descriptor)) {
194
195 return (V) Exceptions.wrap().get(property::getName);
196 }
197 switch (descriptor.getName()) {
198 case "value":
199 return getPropertyValue(property, descriptor.getType());
200
201
202
203
204 case "jcrType":
205 return (V) PropertyType.nameFromValue(Exceptions.wrap().get(property::getType));
206 default:
207 return null;
208 }
209 }
210
211 @Override
212 public <V> void set(Property item, V value, JcrPropertyDescriptor<V> descriptor) {
213 Exceptions.wrap().run(() -> {
214 if (isNodeNameProperty(descriptor)) {
215 handleJcrItemNameChange(item, descriptor.getNodeNameProperty(), String.valueOf(value));
216 } else if ("value".equals(descriptor.getName())) {
217 item.setValue(PropertyUtil.createValue(value, item.getSession().getValueFactory()));
218 } else {
219 throw new IllegalArgumentException("Supports setting only value, jcrName or configured node name property: " + descriptor.getName());
220 }
221 });
222 }
223 }
224
225 private static final NodeNameHelper nodeNameHelper = Components.getComponent(NodeNameHelper.class);
226
227 private static void handleJcrItemNameChange(Item jcrItem, String propertyName, String pendingNewName) throws RepositoryException {
228 if (StringUtils.isNotEmpty(pendingNewName)) {
229 final String validatedName = nodeNameHelper.getValidatedName(pendingNewName);
230 if (jcrItem.isNode()) {
231 final Node item = (Node) jcrItem;
232 if (!jcrItem.getName().equals(validatedName)) {
233 NodeUtil.renameNode(item, validatedName);
234 if (propertyName != null) {
235 PropertyUtil.setProperty(item, propertyName, pendingNewName);
236 }
237 }
238 } else {
239 if (!jcrItem.getName().equals(validatedName)) {
240 PropertyUtil.renameProperty((Property) jcrItem, validatedName);
241 }
242 }
243 }
244 }
245 }