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 info.magnolia.context.Context;
37 import info.magnolia.jcr.predicate.AbstractPredicate;
38 import info.magnolia.jcr.util.NodeUtil;
39 import info.magnolia.ui.api.i18n.I18NAuthoringSupport;
40 import info.magnolia.ui.framework.databinding.ItemProviderStrategy;
41 import info.magnolia.ui.framework.databinding.MultiFieldOrderHandler;
42 import info.magnolia.ui.field.EditorPropertyDefinition;
43
44 import java.util.Collection;
45 import java.util.List;
46 import java.util.Locale;
47 import java.util.Optional;
48 import java.util.stream.Stream;
49 import java.util.stream.StreamSupport;
50
51 import javax.inject.Inject;
52 import javax.inject.Provider;
53 import javax.jcr.Node;
54 import javax.jcr.RepositoryException;
55
56 import org.apache.commons.lang3.RandomStringUtils;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59
60 import lombok.AllArgsConstructor;
61 import lombok.SneakyThrows;
62
63
64
65
66 public class JcrChildNodeOrderHandler implements MultiFieldOrderHandler<Node> {
67
68 private static final Logger log = LoggerFactory.getLogger(JcrChildNodeOrderHandler.class);
69
70 private static final String INCREMENT_REGEX = "(\\d{1,3})";
71
72 private final JcrChildNodeOrderHandlerDefinition definition;
73
74 private final EditorPropertyDefinition propertyDefinition;
75
76 private final I18NAuthoringSupport i18NAuthoringSupport;
77
78 private final Provider<Context> contextProvider;
79
80 @Inject
81 public JcrChildNodeOrderHandler(JcrChildNodeOrderHandlerDefinition definition, EditorPropertyDefinition propertyDefinition, I18NAuthoringSupport i18NAuthoringSupport, Provider<Context> contextProvider) {
82 this.definition = definition;
83 this.propertyDefinition = propertyDefinition;
84 this.i18NAuthoringSupport = i18NAuthoringSupport;
85 this.contextProvider = contextProvider;
86 }
87
88 @Override
89 public Stream<ItemProviderStrategy<Node>> readItems(Node parent, Locale locale) {
90 return getChildNodes(parent, propertyDefinition.getName(), locale).map(node -> new AlwaysFetchJcrNodeFromNewSession(contextProvider, node));
91 }
92
93 @Override
94 @SneakyThrows(RepositoryException.class)
95 public Node getOrCreate(Node parent, String name, Locale locale) {
96 return NodeUtil.createPath(parent, deriveLocaleAwareName(name, locale), definition.getNodeType());
97 }
98
99 @Override
100 public void removeItems(Collection<Node> items) {
101 items.forEach(node -> {
102 try {
103 node.remove();
104 } catch (RepositoryException e) {
105 log.warn("Failed to remove node: {}", e.getMessage());
106 }
107 });
108 }
109
110 @Override
111 @SneakyThrows(RepositoryException.class)
112 public void applyOrder(Node parent, List<Node> itemOrder, Locale locale) {
113 for (int idx = 0; idx < itemOrder.size() - 1; idx++) {
114 NodeUtil.orderAfter(itemOrder.get(idx + 1), itemOrder.get(idx).getName());
115 }
116
117 String baseName = propertyDefinition.getName();
118
119 for (Node node : itemOrder) {
120 String nodeName = deriveLocaleAwareName(baseName + itemOrder.indexOf(node), locale);
121 if (parent.hasNode(nodeName)) {
122 NodeUtil.renameNode(parent.getNode(nodeName), RandomStringUtils.randomAlphanumeric(10));
123 }
124 NodeUtil.renameNode(node, nodeName);
125 }
126 }
127
128
129
130
131 private String childItemRegexRepresentation(String name) {
132 return name + INCREMENT_REGEX;
133 }
134
135 private String deriveLocaleAwareName(String name, Locale locale) {
136 if (i18NAuthoringSupport != null && locale != null && !i18NAuthoringSupport.isDefaultLocale(locale)) {
137 return i18NAuthoringSupport.deriveLocalisedPropertyName(name, locale);
138 }
139 return name;
140 }
141
142 @SneakyThrows(RepositoryException.class)
143 private Stream<Node> getChildNodes(Node node, String name, Locale locale) {
144 return StreamSupport.stream(NodeUtil.getNodes(node, new NodeNameRegexPredicate(name, locale)).spliterator(), false);
145 }
146
147 @AllArgsConstructor
148 private class NodeNameRegexPredicate extends AbstractPredicate<Node> {
149
150 private final String baseName;
151
152 private final Locale locale;
153
154 @Override
155 @SneakyThrows(RepositoryException.class)
156 public boolean evaluateTyped(Node node) {
157 return node.getName().matches(deriveLocaleAwareName(childItemRegexRepresentation(baseName), locale));
158 }
159 }
160
161 private class AlwaysFetchJcrNodeFromNewSession implements ItemProviderStrategy<Node> {
162
163 private final Provider<Context> contextProvider;
164 private final String workspace;
165 private final String identifier;
166
167 @SneakyThrows(RepositoryException.class)
168 AlwaysFetchJcrNodeFromNewSession(Provider<Context> contextProvider, Node node) {
169 this.contextProvider = contextProvider;
170 workspace = node.getSession().getWorkspace().getName();
171 identifier = node.getIdentifier();
172 }
173
174 @Override
175 @SneakyThrows(RepositoryException.class)
176 public Optional<Node> read() {
177 return Optional.of(contextProvider.get().getJCRSession(workspace).getNodeByIdentifier(identifier));
178 }
179 }
180 }