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