1 package de.juplo.thymeleaf;
4 import com.fasterxml.jackson.core.JsonFactory;
5 import de.juplo.jackson.SimpleMapper;
6 import java.io.IOException;
7 import java.util.HashMap;
8 import java.util.Iterator;
11 import java.util.Map.Entry;
12 import java.util.regex.Matcher;
13 import java.util.regex.Pattern;
14 import org.slf4j.Logger;
15 import org.slf4j.LoggerFactory;
16 import org.thymeleaf.Arguments;
17 import org.thymeleaf.Configuration;
18 import org.thymeleaf.TemplateProcessingParameters;
19 import org.thymeleaf.context.IContext;
20 import org.thymeleaf.context.VariablesMap;
21 import org.thymeleaf.dom.Element;
22 import org.thymeleaf.dom.Node;
23 import org.thymeleaf.IEngineConfiguration;
24 import org.thymeleaf.context.ITemplateContext;
25 import org.thymeleaf.engine.AttributeName;
26 import org.thymeleaf.engine.EngineEventUtils;
27 import org.thymeleaf.exceptions.TemplateProcessingException;
28 import org.thymeleaf.model.IProcessableElementTag;
29 import org.thymeleaf.processor.element.AbstractAttributeTagProcessor;
30 import org.thymeleaf.processor.element.IElementTagStructureHandler;
31 import org.thymeleaf.standard.expression.FragmentExpression;
32 import org.thymeleaf.standard.expression.FragmentExpression.ExecutedFragmentExpression;
33 import org.thymeleaf.standard.expression.IStandardExpression;
34 import org.thymeleaf.standard.expression.NoOpToken;
35 import org.thymeleaf.standard.expression.StandardExpressionExecutionContext;
36 import org.thymeleaf.templatemode.TemplateMode;
37 import org.thymeleaf.templateresolver.ITemplateResolver;
38 import org.thymeleaf.templateresolver.TemplateResolution;
43 * Retrievs and parses JSON-data and imports the parsed variables as node-local
47 public class ImportVariablesAttributeProcessor extends AbstractAttributeTagProcessor
49 private static final Logger LOG =
50 LoggerFactory.getLogger(ImportVariablesAttributeProcessor.class);
51 private static final JsonFactory FACTORY = new JsonFactory();
52 private static final String PROPERTY_NAME =
53 ImportVariablesAttributeProcessor.class.getCanonicalName() + "_VARIABLES";
55 public static final Pattern PATTERN =
57 "^\\s*(?:(?:(merge)|replace):)?\\s*(?:(\\{.*\\})|(.*))\\s*$",
58 Pattern.DOTALL | Pattern.CASE_INSENSITIVE
61 public static final String ATTR_NAME = "variables";
62 public static final int ATTR_PRECEDENCE = 200;
65 public ImportVariablesAttributeProcessor(String prefix)
67 super(TemplateMode.HTML, prefix, null, false, ATTR_NAME, true, ATTR_PRECEDENCE, true);
72 protected void doProcess(
73 final ITemplateContext context,
74 final IProcessableElementTag element,
75 final AttributeName name,
76 final String attribute,
77 final IElementTagStructureHandler handler
80 if (attribute == null)
87 final IStandardExpression expression =
88 EngineEventUtils.computeAttributeExpression(
95 if (expression != null && expression instanceof FragmentExpression)
97 final ExecutedFragmentExpression executedFragmentExpression =
98 FragmentExpression.createExecutedFragmentExpression(
100 (FragmentExpression) expression,
101 StandardExpressionExecutionContext.NORMAL
104 FragmentExpression.resolveExecutedFragmentExpression(
106 executedFragmentExpression,
112 result = expression.execute(context);
115 // If the result of this expression is NO-OP, there is nothing to execute
116 if (result == NoOpToken.VALUE)
118 handler.removeAttribute(name);
122 location = result.toString();
124 catch (final TemplateProcessingException e)
126 location = attribute;
130 Iterator<Entry<String, Object>> it = null;
132 Matcher matcher = PATTERN.matcher(location);
133 boolean merge = false;
136 if (matcher.matches())
138 merge = matcher.group(1) != null;
139 json = matcher.group(2);
140 location = matcher.group(3);
145 LOG.info("parsing parameter as JSON");
146 LOG.debug("parameter: {}", json);
149 it = SimpleMapper.getObjectIterator(FACTORY.createParser(json));
151 catch (IOException e)
153 LOG.error("cannot parse parameter as JSON: {}", json, e.getMessage());
159 LOG.info("retriving {} as Spring-resource", location);
160 IEngineConfiguration config = context.getConfiguration();
161 for (ITemplateResolver resolver : config.getTemplateResolvers())
163 TemplateResolution resolution =
164 resolver.resolveTemplate(
166 context.getTemplateData().getTemplate(),
170 if (resolution != null)
174 it = SimpleMapper.getObjectIterator(FACTORY.createParser(resolution.getTemplateResource().reader()));
177 catch (IOException e) {}
183 LOG.error("cannot resolve {} as JSON (not found)!", location);
190 Map<String, Object> variables = getVariables(context);
195 Entry<String, Object> variable = it.next();
196 String key = variable.getKey();
197 Object value = variable.getValue();
198 Object existing = context.getVariable(key);
199 if (existing != null)
201 if (value instanceof String)
203 if (!(existing instanceof String))
206 "cannot merge variable {} of type {} with a string",
210 throw new RuntimeException(
211 "Type-Missmatch for variable " + key
215 String string = ((String)existing).concat((String) value);
216 LOG.info("appending variable to string {}", key);
217 handler.setLocalVariable(key, string);
219 else if (value instanceof Map)
221 if (!(existing instanceof Map))
224 "cannot merge variable {} of type {} with a map",
228 throw new RuntimeException(
229 "Type-Missmatch for variable " + key
233 Map map = ((Map)existing);
234 map.putAll((Map) value);
235 LOG.info("merging variable with map {}", key);
236 handler.setLocalVariable(key, map);
238 else if (value instanceof List)
240 if (!(existing instanceof List))
243 "cannot merge variable {} of type {} with a list",
247 throw new RuntimeException(
248 "Type-Missmatch for variable " + key
252 List list = ((List)existing);
253 list.addAll((List) value);
254 LOG.info("appending contents of variable to list {}", key);
255 handler.setLocalVariable(key, list);
260 "variable {} is of unexpected type {}", key, value.getClass()
262 throw new RuntimeException(
263 "Found variable of unexpected type: " + key
269 LOG.info("adding new variable {}", key);
270 handler.setLocalVariable(key, value);
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 handler.setLocalVariable(key, value);
285 catch (IllegalArgumentException e)
287 LOG.error("cannot parse {} as JSON: {}", location, e.getMessage());
288 throw new RuntimeException(e);
291 handler.removeAttribute(name);
295 Map<String, Object> getVariables(ITemplateContext context)
297 Map<String, Object> variables = new HashMap<>();
302 static Object convert(JsonParser parser) throws IOException
304 JsonToken token = parser.getCurrentToken();
306 fail(parser, "unexpected EOF");
310 case VALUE_STRING: return parser.getText();
311 case VALUE_NUMBER_INT: return parser.getIntValue();
312 case VALUE_NUMBER_FLOAT: return parser.getDoubleValue();
313 case START_OBJECT: return convertObject(parser);
314 case START_ARRAY: return convertArray(parser);
315 case VALUE_TRUE: return Boolean.TRUE;
316 case VALUE_FALSE: return Boolean.FALSE;
317 case VALUE_NULL: return null;
320 fail(parser, "unexpected token " + token.toString());
321 return null; // << Will never be reached, because fail always throws an exception
324 static Map<String, Object> convertObject(JsonParser parser) throws IOException
326 JsonToken token = parser.nextToken();
328 fail(parser, "unexpected EOF");
330 Map<String, Object> map = new LinkedHashMap<>();
332 while (!JsonToken.END_OBJECT.equals(token))
334 if (!JsonToken.FIELD_NAME.equals(token))
335 fail(parser, "expected a field-name");
337 String name = parser.getText();
339 Object value = convert(parser);
340 map.put(name, value);
342 token = parser.nextToken();
344 fail(parser, "unexpected EOF");
350 static List<Object> convertArray(JsonParser parser) throws IOException
352 JsonToken token = parser.nextToken();
354 fail(parser, "unexpected EOF");
356 List<Object> list = new LinkedList<>();
358 while (!JsonToken.END_ARRAY.equals(token))
360 list.add(convert(parser));
362 token = parser.nextToken();
364 fail(parser, "unexpected EOF");
370 static void fail(JsonParser parser, String message)
372 JsonLocation location = parser.getCurrentLocation();
374 "{} at char-offset {} (line {}, column {})",
376 location.getCharOffset(),
377 location.getLineNr(),
378 location.getColumnNr()
380 throw new RuntimeException("Cannot parse JSON: " + message);