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.databinding;
35
36 import static java.util.stream.Collectors.*;
37
38 import info.magnolia.jcr.RuntimeRepositoryException;
39 import info.magnolia.jcr.util.NodeUtil;
40 import info.magnolia.jcr.util.PropertyUtil;
41 import info.magnolia.ui.databinding.JcrItemPropertySet.JcrPropertyDescriptor;
42 import info.magnolia.ui.datasource.jcr.SupportsRenaming;
43 import info.magnolia.util.CollectionConversionCapableBeanUtils;
44 import info.magnolia.util.JcrValueConverter;
45
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.Collection;
49 import java.util.List;
50 import java.util.Objects;
51 import java.util.Optional;
52 import java.util.Set;
53 import java.util.stream.Stream;
54
55 import javax.jcr.InvalidItemStateException;
56 import javax.jcr.Item;
57 import javax.jcr.ItemNotFoundException;
58 import javax.jcr.Node;
59 import javax.jcr.Property;
60 import javax.jcr.RepositoryException;
61 import javax.jcr.Value;
62
63 import org.apache.commons.beanutils.BeanUtilsBean;
64 import org.apache.commons.beanutils.ConversionException;
65 import org.apache.commons.lang3.StringUtils;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
68
69 import lombok.SneakyThrows;
70
71
72
73
74
75
76
77
78 abstract class JcrItemInteractionStrategyImpl<I extends Item> implements JcrItemInteractionStrategy<I> {
79
80 private static final Logger log = LoggerFactory.getLogger(JcrItemInteractionStrategy.class);
81
82 private BeanUtilsBean beanUtils = new CollectionConversionCapableBeanUtils();
83
84 @SneakyThrows(RepositoryException.class)
85 <V> V getPropertyValue(Property property, Class<V> type) {
86 try {
87 boolean multiValue = property.isMultiple();
88
89 if (multiValue) {
90
91 final Value[] values = property.getValues();
92 final Set<Object> unpackedValues =
93 Stream.of(values)
94 .map(JcrValueConverter::read)
95 .collect(toSet());
96
97
98
99 return (V) beanUtils.getConvertUtils().convert(unpackedValues, type);
100 } else {
101 return convertToPresentation(JcrValueConverter.read(property.getValue()), type).orElse(null);
102 }
103 } catch (InvalidItemStateException | ItemNotFoundException e) {
104 log.debug("Failed to get the value of property [{}], returning null", property, e);
105 return null;
106 }
107 }
108
109 <V> void setPropertyValue(Node node, String name, V value) {
110 if ("jcrName".equals(name)) {
111 if (node instanceof SupportsRenaming) {
112 ((SupportsRenaming) node).rename(String.valueOf(value));
113 }
114 }
115
116 try {
117 if (node.hasProperty(name)) {
118 if (propertyValueEquals(node.getProperty(name), value)) {
119 return;
120 }
121 }
122
123 PropertyUtil.setProperty(node, name, value);
124 } catch (RepositoryException e) {
125 throw new RuntimeRepositoryException(e);
126 }
127 }
128
129 private <V> boolean propertyValueEquals(Property property, V value) throws RepositoryException {
130 if (!property.isMultiple()) {
131 return Objects.equals(JcrValueConverter.read(property.getValue()), value);
132 } else {
133 final Value[] values = property.getValues();
134 final List<Object> jcrValues = Arrays.stream(values).map(JcrValueConverter::read).collect(toList());
135 return value instanceof Collection && Objects.equals(jcrValues, new ArrayList<>((Collection<?>) value));
136 }
137 }
138
139 protected <V> Optional<V> convertToPresentation(Object value, Class<V> targetType) {
140 try {
141
142 return Optional.ofNullable((V) beanUtils.getConvertUtils().convert(value, targetType));
143 } catch (ConversionException e) {
144 return Optional.empty();
145 }
146 }
147
148
149
150
151 final static class WithNodes extends JcrItemInteractionStrategyImpl<Node> {
152
153 @Override
154 public <V> V get(Node node, JcrPropertyDescriptor<V> descriptor) {
155 final Property property = PropertyUtil.getPropertyOrNull(node, descriptor.getName());
156 if ("jcrName".equals(descriptor.getName()) && property == null) {
157
158 return (V) NodeUtil.getName(node);
159 }
160 return Optional.ofNullable(property)
161 .map(prop -> getPropertyValue(prop, descriptor.getType()))
162 .orElse(null);
163 }
164
165 @Override
166 public <V> void set(Node node, V value, JcrPropertyDescriptor<V> descriptor) {
167 if (notNullOrEmptyString(value)) {
168 setPropertyValue(node, descriptor.getName(), value);
169 } else {
170
171 Optional.ofNullable(PropertyUtil.getPropertyOrNull(node, descriptor.getName())).ifPresent(property -> {
172 try {
173 property.remove();
174 } catch (RepositoryException e) {
175 throw new RuntimeRepositoryException(e);
176 }
177 });
178 }
179 }
180
181 private boolean notNullOrEmptyString(Object value) {
182 if (value instanceof String) {
183 return StringUtils.isNotEmpty((String) value);
184 }
185 return value != null;
186 }
187 }
188
189
190
191
192 final static class WithProperties extends JcrItemInteractionStrategyImpl<Property> {
193
194 @Override
195 @SneakyThrows(RepositoryException.class)
196 public <V> V get(Property property, JcrPropertyDescriptor<V> descriptor) {
197 switch (descriptor.getName()) {
198 case "value":
199 return getPropertyValue(property, descriptor.getType());
200
201
202
203
204 case "jcrName": case "name":
205
206 return (V) property.getName();
207 default:
208 return null;
209 }
210 }
211
212 @Override
213 public <V> void set(Property item, V value, JcrPropertyDescriptor<V> descriptor) {
214 try {
215 switch (descriptor.getName()) {
216 case "value":
217 setPropertyValue(item.getParent(), item.getName(), value);
218 break;
219 case "jcrName":
220 case "name":
221 if (!(value instanceof String) || String.valueOf(value).isEmpty()) {
222 return;
223 }
224
225 if (item instanceof SupportsRenaming && !Objects.equals(item.getName(), value)) {
226 ((SupportsRenaming) item).rename(String.valueOf(value));
227 }
228
229 break;
230 }
231 } catch (RepositoryException e) {
232 log.warn("Failed to set the value [{}] to [{}]...", e);
233 }
234 }
235 }
236 }