1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package io.wcm.maven.plugins.jsondlgcnv;
21
22 import java.io.IOException;
23 import java.nio.charset.StandardCharsets;
24 import java.util.ArrayList;
25 import java.util.Iterator;
26 import java.util.LinkedHashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.TreeMap;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33
34 import org.apache.commons.io.FileUtils;
35 import org.apache.commons.lang3.StringUtils;
36 import org.apache.maven.plugin.logging.Log;
37 import org.apache.sling.api.resource.Resource;
38 import org.apache.sling.api.resource.ResourceResolver;
39 import org.apache.sling.api.resource.ValueMap;
40 import org.apache.sling.commons.json.JSONArray;
41 import org.apache.sling.commons.json.JSONException;
42 import org.apache.sling.commons.json.JSONObject;
43 import org.apache.sling.fsprovider.internal.mapper.ContentFile;
44 import org.apache.sling.testing.mock.sling.junit.SlingContext;
45
46 import com.google.gson.Gson;
47 import com.google.gson.GsonBuilder;
48 import com.google.gson.JsonObject;
49
50
51
52
53
54
55
56 class DialogConverter {
57
58
59 private static final Pattern MAPPED_PATTERN = Pattern.compile("^(\\!{0,1})\\$\\{(\'.*?\'|.*?)(:(.+))?\\}$");
60
61
62 private static final String PROPERTY_MAP_CHILDREN = "cq:rewriteMapChildren";
63 private static final String PROPERTY_IS_FINAL = "cq:rewriteFinal";
64 private static final String PROPERTY_COMMON_ATTRS = "cq:rewriteCommonAttrs";
65 private static final String PROPERTY_RENDER_CONDITION = "cq:rewriteRenderCondition";
66
67
68 private static final String NN_CQ_REWRITE_PROPERTIES = "cq:rewriteProperties";
69
70
71 private static final String NN_RENDER_CONDITION = "rendercondition";
72 private static final String NN_GRANITE_RENDER_CONDITION = "granite:rendercondition";
73 private static final String NN_GRANITE_DATA = "granite:data";
74
75
76 private static final String[] GRANITE_COMMON_ATTR_PROPERTIES = { "id", "rel", "class", "title", "hidden", "itemscope", "itemtype", "itemprop" };
77 private static final String RENDER_CONDITION_CORAL2_RESOURCE_TYPE_PREFIX = "granite/ui/components/foundation/renderconditions";
78 private static final String RENDER_CONDITION_CORAL3_RESOURCE_TYPE_PREFIX = "granite/ui/components/coral/foundation/renderconditions";
79 private static final String DATA_PREFIX = "data-";
80
81 private final Rules rules;
82 private final Resource sourceRoot;
83 private final Log log;
84
85 DialogConverter(SlingContext context, String rulesPath, Log log) {
86 this.rules = new Rules(context.resourceResolver().getResource(rulesPath));
87 this.sourceRoot = context.resourceResolver().getResource("/source");
88 this.log = log;
89 }
90
91
92
93
94 public void convert() {
95 traverse(sourceRoot, true);
96 }
97
98
99
100
101 public void format() {
102 traverse(sourceRoot, false);
103 }
104
105 private void traverse(Resource resource, boolean convert) {
106 checkRuleMatch(resource, convert);
107 Iterator<Resource> children = resource.listChildren();
108 while (children.hasNext()) {
109 traverse(children.next(), convert);
110 }
111 }
112
113 @SuppressWarnings("null")
114 private void checkRuleMatch(Resource resource, boolean convert) {
115 Rule rule = rules.getRule(resource);
116 if (rule != null) {
117 if (log.isInfoEnabled()) {
118 log.info("Convert " + StringUtils.removeStart(resource.getPath(), "/source/") + " with rule '" + rule.getName() + "'.");
119 }
120
121 ContentFile contentFile = resource.adaptTo(ContentFile.class);
122 try {
123 JSONObject jsonContent = new JSONObject(FileUtils.readFileToString(contentFile.getFile(), StandardCharsets.UTF_8));
124 JsonElement wrapper = getJsonElement(jsonContent, contentFile.getSubPath());
125
126 if (convert) {
127 applyRule(wrapper, rule);
128 }
129
130
131 Gson gson = new GsonBuilder().setPrettyPrinting().create();
132 JsonObject gsonJson = gson.fromJson(jsonContent.toString(), JsonObject.class);
133
134 FileUtils.write(contentFile.getFile(), gson.toJson(gsonJson), StandardCharsets.UTF_8);
135 }
136 catch (JSONException | IOException ex) {
137 throw new RuntimeException(ex);
138 }
139 }
140 }
141
142 private JsonElement getJsonElement(JSONObject json, String path) throws JSONException {
143 if (StringUtils.isEmpty(path)) {
144 return new JsonElement(json, null, null);
145 }
146 if (StringUtils.contains(path, "/")) {
147 String name = StringUtils.substringBefore(path, "/");
148 String remainder = StringUtils.substringAfter(path, "/");
149 JSONObject child = json.getJSONObject(name);
150 return getJsonElement(child, remainder);
151 }
152 else {
153 return new JsonElement(json.getJSONObject(path), path, json);
154 }
155 }
156
157 private void applyRule(JsonElement wrapper, Rule rule) throws JSONException {
158
159 Resource replacement = rule.getReplacement();
160 if (replacement == null) {
161 throw new RuntimeException("The rule " + rule + " does not define a 'replacement' section.");
162 }
163 ValueMap replacementProps = replacement.getValueMap();
164
165
166
167 if (!replacement.hasChildren()) {
168 wrapper.parent.remove(wrapper.key);
169 return;
170 }
171 JSONObject root = cloneJson(wrapper.element);
172
173
174
175 boolean treeIsFinal = replacementProps.get(PROPERTY_IS_FINAL, false);
176
177
178 Resource replacementNext = replacement.listChildren().next();
179 JSONObject copy = wrapper.element;
180 clearJson(copy);
181 copyToJson(copy, replacementNext);
182
183
184 if (replacementProps.containsKey(PROPERTY_COMMON_ATTRS)) {
185 addCommonAttrMappings(root, copy);
186 }
187
188
189 if (replacementProps.containsKey(PROPERTY_RENDER_CONDITION)) {
190 if (root.has(NN_GRANITE_RENDER_CONDITION) || root.has(NN_RENDER_CONDITION)) {
191 JSONObject renderConditionRoot = root.has(NN_GRANITE_RENDER_CONDITION) ? root.getJSONObject(NN_GRANITE_RENDER_CONDITION)
192 : root.getJSONObject(NN_RENDER_CONDITION);
193 JSONObject renderConditionCopy = copy.put(NN_GRANITE_RENDER_CONDITION, renderConditionRoot);
194
195
196 Iterator<JSONObject> renderConditionIterator = collectTree(renderConditionCopy).iterator();
197
198 while (renderConditionIterator.hasNext()) {
199 JSONObject renderConditionNode = renderConditionIterator.next();
200 String resourceType = renderConditionNode.getString(ResourceResolver.PROPERTY_RESOURCE_TYPE);
201 if (resourceType.startsWith(RENDER_CONDITION_CORAL2_RESOURCE_TYPE_PREFIX)) {
202 resourceType = resourceType.replace(RENDER_CONDITION_CORAL2_RESOURCE_TYPE_PREFIX, RENDER_CONDITION_CORAL3_RESOURCE_TYPE_PREFIX);
203 renderConditionNode.put(ResourceResolver.PROPERTY_RESOURCE_TYPE, resourceType);
204 }
205 }
206 }
207 }
208
209
210 Map<String, JSONObject> mappings = new LinkedHashMap<>();
211
212 Iterator<JSONObject> nodeIterator = collectTree(copy).iterator();
213 while (nodeIterator.hasNext()) {
214 JSONObject node = nodeIterator.next();
215
216 Map<String, Object> replacementProperties = getProperties(node);
217 Iterator<Map.Entry<String, Object>> propertyIterator = replacementProperties.entrySet().iterator();
218 JSONObject rewritePropertiesNode = null;
219
220 if (node.has(NN_CQ_REWRITE_PROPERTIES)) {
221 rewritePropertiesNode = node.getJSONObject(NN_CQ_REWRITE_PROPERTIES);
222 }
223
224 while (propertyIterator.hasNext()) {
225 Map.Entry<String, Object> property = propertyIterator.next();
226
227 if (PROPERTY_MAP_CHILDREN.equals(property.getKey())) {
228 mappings.put(cleanup((String)property.getValue()), node);
229
230 node.remove(cleanup(property.getKey()));
231 continue;
232 }
233
234 if (PROPERTY_IS_FINAL.equals(property.getKey())) {
235 if (!treeIsFinal) {
236
237
238 }
239 node.remove(cleanup(property.getKey()));
240 continue;
241 }
242
243 boolean mappedProperty = mapProperty(root, node, property.getKey(), (String)property.getValue());
244
245 if (mappedProperty && rewritePropertiesNode != null) {
246 if (rewritePropertiesNode.has(cleanup(property.getKey()))) {
247 rewriteProperty(node, cleanup(property.getKey()), rewritePropertiesNode.getJSONArray(cleanup(property.getKey())));
248 }
249 }
250 }
251
252
253 if (rewritePropertiesNode != null) {
254 node.remove(NN_CQ_REWRITE_PROPERTIES);
255 }
256
257
258 reorderChildrenProperties(node, root);
259 }
260
261
262 for (Map.Entry<String, JSONObject> mapping : mappings.entrySet()) {
263 String key = cleanup(mapping.getKey());
264 if (!root.has(cleanup(key))) {
265
266 continue;
267 }
268 JSONObject source = root.getJSONObject(cleanup(key));
269 JSONObject destination = mapping.getValue();
270 Iterator<Map.Entry<String,JSONObject>> iterator = getChildren(source).entrySet().iterator();
271
272 while (iterator.hasNext()) {
273 Map.Entry<String,JSONObject> child = iterator.next();
274 destination.put(cleanup(child.getKey()), child.getValue());
275 }
276 }
277
278
279
280
281
282
283
284
285
286
287
288 }
289
290
291
292
293
294
295
296
297 private boolean mapProperty(JSONObject root, JSONObject node, String key, String... mapping) throws JSONException {
298 boolean deleteProperty = false;
299 for (String value : mapping) {
300 Matcher matcher = MAPPED_PATTERN.matcher(value);
301 if (matcher.matches()) {
302
303
304 deleteProperty = true;
305 String path = matcher.group(2);
306
307 path = StringUtils.removeStart(StringUtils.stripEnd(path, "\'"), "\'");
308 if (root.has(cleanup(path))) {
309
310 Object originalValue = root.get(cleanup(path));
311 node.put(cleanup(key), originalValue);
312
313
314 String negate = matcher.group(1);
315 if ("!".equals(negate) && (originalValue instanceof Boolean)) {
316 node.put(cleanup(key), !((Boolean)originalValue));
317 }
318
319
320 deleteProperty = false;
321 break;
322 }
323 else {
324 String defaultValue = matcher.group(4);
325 if (defaultValue != null) {
326 node.put(cleanup(key), defaultValue);
327 deleteProperty = false;
328 break;
329 }
330 }
331 }
332 }
333 if (deleteProperty) {
334
335 node.remove(key);
336 return false;
337 }
338
339 return true;
340 }
341
342
343
344
345
346
347
348 private void rewriteProperty(JSONObject node, String key, JSONArray rewriteProperty) throws JSONException {
349 if (node.get(cleanup(key)) instanceof String) {
350 if (rewriteProperty.length() == 2) {
351 if (rewriteProperty.get(0) instanceof String && rewriteProperty.get(1) instanceof String) {
352 String pattern = rewriteProperty.getString(0);
353 String replacement = rewriteProperty.getString(1);
354
355 Pattern compiledPattern = Pattern.compile(pattern);
356 Matcher matcher = compiledPattern.matcher(node.getString(cleanup(key)));
357 node.put(cleanup(key), matcher.replaceAll(replacement));
358 }
359 }
360 }
361 }
362
363
364
365
366
367
368 private void addCommonAttrMappings(JSONObject root, JSONObject node) throws JSONException {
369 for (String property : GRANITE_COMMON_ATTR_PROPERTIES) {
370 String[] mapping = { "${./" + property + "}", "${\'./granite:" + property + "\'}" };
371 mapProperty(root, node, "granite:" + property, mapping);
372 }
373
374 if (root.has(NN_GRANITE_DATA)) {
375
376 node.put(NN_GRANITE_DATA, root.get(NN_GRANITE_DATA));
377 }
378
379
380 for (Map.Entry<String, Object> entry : getProperties(root).entrySet()) {
381 if (!StringUtils.startsWith(entry.getKey(), DATA_PREFIX)) {
382 continue;
383 }
384
385
386 JSONObject dataNode;
387 if (!node.has(NN_GRANITE_DATA)) {
388 dataNode = new JSONObject();
389 node.put(NN_GRANITE_DATA, dataNode);
390 }
391 else {
392 dataNode = node.getJSONObject(NN_GRANITE_DATA);
393 }
394
395
396 String nameWithoutPrefix = entry.getKey().substring(DATA_PREFIX.length());
397 mapProperty(root, dataNode, nameWithoutPrefix, "${./" + entry.getKey() + "}");
398 }
399 }
400
401 private JSONObject cloneJson(JSONObject item) throws JSONException {
402 JSONObject newItem = new JSONObject();
403
404 Set<Map.Entry<String, Object>> props = getProperties(item).entrySet();
405 for (Map.Entry<String, Object> prop : props) {
406 newItem.put(prop.getKey(), prop.getValue());
407 }
408
409 Set<Map.Entry<String, JSONObject>> children = getChildren(item).entrySet();
410 for (Map.Entry<String, JSONObject> child : children) {
411 newItem.put(child.getKey(), cloneJson(child.getValue()));
412 }
413
414 return newItem;
415 }
416
417 private void clearJson(JSONObject item) throws JSONException {
418 Set<Map.Entry<String, Object>> props = getProperties(item).entrySet();
419 Set<Map.Entry<String, JSONObject>> children = getChildren(item).entrySet();
420 for (Map.Entry<String, Object> prop : props) {
421 item.remove(prop.getKey());
422 }
423 for (Map.Entry<String, JSONObject> child : children) {
424 item.remove(child.getKey());
425 }
426 }
427
428 private void copyToJson(JSONObject dest, Resource resource) throws JSONException {
429 for (Map.Entry<String, Object> entry : resource.getValueMap().entrySet()) {
430 if (StringUtils.equals(cleanup(entry.getKey()), "jcr:primaryType")) {
431 continue;
432 }
433 dest.put(cleanup(entry.getKey()), entry.getValue());
434 }
435
436 Iterator<Resource> children = resource.listChildren();
437 while (children.hasNext()) {
438 Resource child = children.next();
439 JSONObject childObject = new JSONObject();
440 copyToJson(childObject, child);
441 dest.put(child.getName(), childObject);
442 }
443 }
444
445 private List<JSONObject> collectTree(JSONObject item) throws JSONException {
446 List<JSONObject> items = new ArrayList<>();
447 items.add(item);
448 for (JSONObject child : getChildren(item).values()) {
449 items.addAll(collectTree(child));
450 }
451 return items;
452 }
453
454 private Map<String, Object> getProperties(JSONObject item) throws JSONException {
455 Map<String, Object> props = new LinkedHashMap<>();
456 JSONArray names = item.names();
457 if (names != null) {
458 for (int i = 0; i < names.length(); i++) {
459 String name = names.getString(i);
460 Object value = item.get(name);
461 if (!(value instanceof JSONObject)) {
462 props.put(name, value);
463 }
464 }
465 }
466 return props;
467 }
468
469 private Map<String, JSONObject> getChildren(JSONObject item) throws JSONException {
470 Map<String, JSONObject> children = new LinkedHashMap<>();
471 JSONArray names = item.names();
472 if (names != null) {
473 for (int i = 0; i < names.length(); i++) {
474 String name = names.getString(i);
475 Object value = item.get(name);
476 if (value instanceof JSONObject) {
477 children.put(name, (JSONObject)value);
478 }
479 }
480 }
481 return children;
482 }
483
484 private String cleanup(String name) {
485 return StringUtils.removeStart(name, "./");
486 }
487
488 private void reorderChildrenProperties(JSONObject item, JSONObject orginal) throws JSONException {
489 Map<String, Object> props = new TreeMap<String, Object>(getProperties(item));
490 Map<String, JSONObject> children = getChildren(item);
491 for (String key : props.keySet()) {
492 item.remove(key);
493 }
494 for (String key : children.keySet()) {
495 item.remove(key);
496 }
497
498 for (String key : getProperties(orginal).keySet()) {
499 if (props.containsKey(key)) {
500 item.put(key, props.get(key));
501 props.remove(key);
502 }
503 }
504
505 for (Map.Entry<String, Object> entry : props.entrySet()) {
506 item.put(entry.getKey(), entry.getValue());
507 }
508
509 for (String key : getProperties(orginal).keySet()) {
510 if (children.containsKey(key)) {
511 item.put(key, children.get(key));
512 children.remove(key);
513 }
514 }
515
516 for (Map.Entry<String, JSONObject> entry : children.entrySet()) {
517 item.put(entry.getKey(), entry.getValue());
518 }
519 }
520
521
522 private static class JsonElement {
523
524 private final JSONObject element;
525 private final String key;
526 private final JSONObject parent;
527
528 JsonElement(JSONObject element, String key, JSONObject parent) {
529 this.element = element;
530 this.key = key;
531 this.parent = parent;
532 }
533
534 }
535
536 }