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