TMP
[juplo-dialect] / src / main / java / de / juplo / thymeleaf / ImportVariablesAttrProcessor.java
1 package de.juplo.thymeleaf;
2
3
4 import com.fasterxml.jackson.core.JsonFactory;
5 import de.juplo.jackson.SimpleMapper;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.util.HashMap;
9 import java.util.Iterator;
10 import java.util.List;
11 import java.util.Locale;
12 import java.util.Map;
13 import java.util.Map.Entry;
14 import java.util.regex.Matcher;
15 import java.util.regex.Pattern;
16 import org.slf4j.Logger;
17 import org.slf4j.LoggerFactory;
18 import org.thymeleaf.context.IContext;
19 import org.thymeleaf.exceptions.TemplateProcessingException;
20 import org.thymeleaf.processor.element.AbstractAttributeModelProcessor;
21 import org.thymeleaf.standard.expression.IStandardExpression;
22 import org.thymeleaf.standard.expression.IStandardExpressionParser;
23 import org.thymeleaf.standard.expression.StandardExpressions;
24 import org.thymeleaf.templatemode.TemplateMode;
25 import org.thymeleaf.templateresolver.ITemplateResolver;
26 import org.thymeleaf.templateresolver.TemplateResolution;
27
28
29
30 /**
31  * Retrievs and parses JSON-data and imports the parsed variables as node-local
32  * variables.
33  * @author Kai Moritz
34  */
35 public class ImportVariablesAttrProcessor extends AbstractAttributeModelProcessor
36 {
37   private static final Logger LOG =
38       LoggerFactory.getLogger(ImportVariablesAttrProcessor.class);
39   private static final JsonFactory FACTORY = new JsonFactory();
40   private static final String PROPERTY_NAME =
41       ImportVariablesAttrProcessor.class.getCanonicalName() + "_VARIABLES";
42
43   public static final Pattern PATTERN =
44       Pattern.compile(
45           "^\\s*(?:(?:(merge)|replace):)?\\s*(?:(\\{.*\\})|(.*))\\s*$",
46           Pattern.DOTALL | Pattern.CASE_INSENSITIVE
47           );
48   public static final int ATTR_PRECEDENCE = 200;
49
50
51   public ImportVariablesAttrProcessor(
52       final String prefix,
53       final String attribute,
54       final String substitute
55       )
56   {
57     super(
58         TemplateMode.HTML,
59         prefix,
60         null,
61         false,
62         attribute,
63         true,
64         ATTR_PRECEDENCE,
65         false
66         );
67   }
68
69
70   @Override
71   public final ProcessorResult processAttribute(
72       final Arguments arguments,
73       final Element element,
74       final String name
75       )
76   {
77     Configuration config = arguments.getConfiguration();
78
79     Configuration configuration = arguments.getConfiguration();
80
81     String parameter = element.getAttributeValue(name);
82     try
83     {
84       IStandardExpressionParser parser =
85           StandardExpressions.getExpressionParser(configuration);
86       IStandardExpression expression =
87           parser.parseExpression(configuration, arguments, parameter);
88       parameter = (String)expression.execute(configuration, arguments);
89     }
90     catch (TemplateProcessingException e) { }
91
92     if (parameter != null && !parameter.trim().isEmpty())
93     {
94       LOG.info("ignoring empty parameter");
95       return ProcessorResult.OK;
96     }
97
98     Iterator<Entry<String, Object>> it = null;
99
100     Matcher matcher = PATTERN.matcher(parameter);
101     boolean merge = false;
102     String json = null;
103     String resource = parameter;
104
105     if (matcher.matches())
106     {
107       merge = matcher.group(1) != null;
108       json = matcher.group(2);
109       resource = matcher.group(3);
110     }
111
112     if (json != null)
113     {
114       LOG.info("parsing parameter as JSON");
115       LOG.debug("parameter: {}", json);
116       try
117       {
118         it = SimpleMapper.getObjectIterator(FACTORY.createParser(json));
119       }
120       catch (IOException e)
121       {
122         LOG.error("cannot parse parameter as JSON: {}", json, e.getMessage());
123         throw new RuntimeException(e);
124       }
125     }
126     else
127     {
128       LOG.info("retriving {} as JSON-template", resource);
129       TemplateProcessingParameters params =
130           new TemplateProcessingParameters(
131               config,
132               resource,
133               new IContext() // << We will not execute the template, hence we need no context
134               {
135                 @Override
136                 public VariablesMap<String, Object> getVariables()
137                 {
138                   return new VariablesMap<>();
139                 }
140
141                 @Override
142                 public Locale getLocale()
143                 {
144                   return Locale.getDefault();
145                 }
146
147                 @Override
148                 public void addContextExecutionInfo(String templateName)
149                 {
150                 }
151               });
152
153       for (ITemplateResolver t_resolver : config.getTemplateResolvers())
154       {
155         TemplateResolution resolution = t_resolver.resolveTemplate(params);
156         if (resolution == null)
157           continue;
158         if (!"JSON".equals(resolution.getTemplateMode()))
159           continue;
160         IResourceResolver r_resolver = resolution.getResourceResolver();
161         InputStream is =
162             r_resolver.getResourceAsStream(params, resolution.getResourceName());
163         if (is == null)
164           continue;
165
166         try
167         {
168           it = SimpleMapper.getObjectIterator(FACTORY.createParser(is));
169           break;
170         }
171         catch (IOException | IllegalArgumentException e)
172         {
173           LOG.error("cannot retreive {} as JSON: {}", parameter, e.getMessage());
174           throw new RuntimeException(e);
175         }
176       }
177
178       if (it == null)
179       {
180         LOG.error("cannot resolve {} as JSON (not found)!", parameter);
181         throw new RuntimeException("Template not found: " + parameter);
182       }
183     }
184
185     try
186     {
187       Map<String, Object> variables = getVariables(element);
188       if (merge)
189       {
190         while(it.hasNext())
191         {
192           Entry<String, Object> variable = it.next();
193           String key = variable.getKey();
194           Object value = variable.getValue();
195           Object existing = variables.get(key);
196           if (existing != null)
197           {
198             if (value instanceof String)
199             {
200               if (!(existing instanceof String))
201               {
202                 LOG.error(
203                     "cannot merge variable {} of type {} with a string",
204                     key,
205                     existing.getClass()
206                     );
207                 throw new RuntimeException(
208                     "Type-Missmatch for variable  " + key
209                     );
210               }
211
212               String string = ((String)existing).concat((String) value);
213               LOG.info("appending variable to string {}", key);
214               element.setNodeLocalVariable(key, string);
215             }
216             else if (value instanceof Map)
217             {
218               if (!(existing instanceof Map))
219               {
220                 LOG.error(
221                     "cannot merge variable {} of type {} with a map",
222                     key,
223                     existing.getClass()
224                     );
225                 throw new RuntimeException(
226                     "Type-Missmatch for variable  " + key
227                     );
228               }
229
230               Map map = ((Map)existing);
231               map.putAll((Map) value);
232               LOG.info("merging variable with map {}", key);
233               element.setNodeLocalVariable(key, map);
234             }
235             else if (value instanceof List)
236             {
237               if (!(existing instanceof List))
238               {
239                 LOG.error(
240                     "cannot merge variable {} of type {} with a list",
241                     key,
242                     existing.getClass()
243                     );
244                 throw new RuntimeException(
245                     "Type-Missmatch for variable  " + key
246                     );
247               }
248
249               List list = ((List)existing);
250               list.addAll((List) value);
251               LOG.info("appending contents of variable to list {}", key);
252               element.setNodeLocalVariable(key, list);
253             }
254             else
255             {
256               LOG.error(
257                   "variable {} is of unexpected type {}", key, value.getClass()
258                   );
259               throw new RuntimeException(
260                   "Found variable of unexpected type: " + key
261                   );
262             }
263           }
264           else
265           {
266             LOG.info("adding new variable {}", key);
267             element.setNodeLocalVariable(key, value);
268           }
269         }
270       }
271       else
272         while(it.hasNext())
273         {
274           Entry<String, Object> variable = it.next();
275           String key = variable.getKey();
276           Object value = variable.getValue();
277           LOG.info("adding variable {}", key);
278           variables.put(key, value);
279           element.setNodeLocalVariable(key, value);
280         }
281     }
282     catch (IllegalArgumentException e)
283     {
284       LOG.error("cannot parse {} as JSON: {}", parameter, e.getMessage());
285       throw new RuntimeException(e);
286     }
287
288     element.removeAttribute(name);
289
290     return ProcessorResult.OK;
291   }
292
293
294   Map<String, Object> getVariables(Node node)
295   {
296     Node parent = node;
297     do
298     {
299       Map<String, Object> variables =
300           (Map<String, Object>)parent.getNodeProperty(PROPERTY_NAME);
301
302       if (variables != null)
303         return variables;
304
305       parent = parent.getParent();
306     }
307     while (parent != null);
308
309     Map<String, Object> variables = new HashMap<>();
310     node.setNodeProperty(PROPERTY_NAME, variables);
311     return variables;
312   }
313
314
315   @Override
316   public int getPrecedence()
317   {
318     return ATTR_PRECEDENCE;
319   }
320 }