1 package de.juplo.thymeleaf;
4 import com.fasterxml.jackson.core.JsonFactory;
5 import de.juplo.simplemapper.SimpleMapper;
6 import java.io.IOException;
7 import java.util.HashMap;
8 import java.util.Iterator;
9 import java.util.LinkedHashMap;
10 import java.util.LinkedList;
11 import java.util.List;
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.IEngineConfiguration;
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.IProcessableElementTag;
24 import org.thymeleaf.processor.element.AbstractAttributeTagProcessor;
25 import org.thymeleaf.processor.element.IElementTagStructureHandler;
26 import org.thymeleaf.standard.expression.FragmentExpression;
27 import org.thymeleaf.standard.expression.FragmentExpression.ExecutedFragmentExpression;
28 import org.thymeleaf.standard.expression.IStandardExpression;
29 import org.thymeleaf.standard.expression.NoOpToken;
30 import org.thymeleaf.standard.expression.StandardExpressionExecutionContext;
31 import org.thymeleaf.templatemode.TemplateMode;
32 import org.thymeleaf.templateresolver.ITemplateResolver;
33 import org.thymeleaf.templateresolver.TemplateResolution;
38 * Retrievs and parses JSON-data and imports the parsed variables as node-local
42 public class ImportVariablesAttributeProcessor extends AbstractAttributeTagProcessor
44 private static final Logger LOG =
45 LoggerFactory.getLogger(ImportVariablesAttributeProcessor.class);
46 private static final JsonFactory FACTORY = new JsonFactory();
47 private static final String PROPERTY_NAME =
48 ImportVariablesAttributeProcessor.class.getCanonicalName() + "_VARIABLES";
50 public static final Pattern PATTERN =
52 "^\\s*(?:(?:(merge)|replace):)?\\s*(?:(\\{.*\\})|(.*))\\s*$",
53 Pattern.DOTALL | Pattern.CASE_INSENSITIVE
56 public static final String ATTR_NAME = "variables";
57 public static final int ATTR_PRECEDENCE = 200;
60 public ImportVariablesAttributeProcessor(String prefix)
62 super(TemplateMode.HTML, prefix, null, false, ATTR_NAME, true, ATTR_PRECEDENCE, true);
67 protected void doProcess(
68 final ITemplateContext context,
69 final IProcessableElementTag element,
70 final AttributeName name,
71 final String attribute,
72 final IElementTagStructureHandler handler
75 if (attribute == null)
82 final IStandardExpression expression =
83 EngineEventUtils.computeAttributeExpression(
90 if (expression != null && expression instanceof FragmentExpression)
92 final ExecutedFragmentExpression executedFragmentExpression =
93 FragmentExpression.createExecutedFragmentExpression(
95 (FragmentExpression) expression,
96 StandardExpressionExecutionContext.NORMAL
99 FragmentExpression.resolveExecutedFragmentExpression(
101 executedFragmentExpression,
107 result = expression.execute(context);
110 // If the result of this expression is NO-OP, there is nothing to execute
111 if (result == NoOpToken.VALUE)
113 handler.removeAttribute(name);
117 location = result.toString();
119 catch (final TemplateProcessingException e)
121 location = attribute;
125 Iterator<Entry<String, Object>> it = null;
127 Matcher matcher = PATTERN.matcher(location);
128 boolean merge = false;
131 if (matcher.matches())
133 merge = matcher.group(1) != null;
134 json = matcher.group(2);
135 location = matcher.group(3);
140 LOG.info("parsing parameter as JSON");
141 LOG.debug("parameter: {}", json);
144 it = SimpleMapper.getObjectIterator(FACTORY.createParser(json));
146 catch (IOException e)
148 LOG.error("cannot parse parameter as JSON: {}", json, e.getMessage());
154 LOG.info("retriving {} as Spring-resource", location);
155 IEngineConfiguration config = context.getConfiguration();
156 for (ITemplateResolver resolver : config.getTemplateResolvers())
158 TemplateResolution resolution =
159 resolver.resolveTemplate(
161 context.getTemplateData().getTemplate(),
165 if (resolution != null)
169 it = SimpleMapper.getObjectIterator(FACTORY.createParser(resolution.getTemplateResource().reader()));
172 catch (IOException e) {}
178 LOG.error("cannot resolve {} as JSON (not found)!", location);
185 Map<String, Object> variables = getVariables(context);
190 Entry<String, Object> variable = it.next();
191 String key = variable.getKey();
192 Object value = variable.getValue();
193 Object existing = context.getVariable(key);
194 if (existing != null)
196 if (value instanceof String)
198 if (!(existing instanceof String))
201 "cannot merge variable {} of type {} with a string",
205 throw new RuntimeException(
206 "Type-Missmatch for variable " + key
210 String string = ((String)existing).concat((String) value);
211 LOG.info("appending variable to string {}", key);
212 handler.setLocalVariable(key, string);
214 else if (value instanceof Map)
216 if (!(existing instanceof Map))
219 "cannot merge variable {} of type {} with a map",
223 throw new RuntimeException(
224 "Type-Missmatch for variable " + key
228 Map map = ((Map)existing);
229 map.putAll((Map) value);
230 LOG.info("merging variable with map {}", key);
231 handler.setLocalVariable(key, map);
233 else if (value instanceof List)
235 if (!(existing instanceof List))
238 "cannot merge variable {} of type {} with a list",
242 throw new RuntimeException(
243 "Type-Missmatch for variable " + key
247 List list = ((List)existing);
248 list.addAll((List) value);
249 LOG.info("appending contents of variable to list {}", key);
250 handler.setLocalVariable(key, list);
255 "variable {} is of unexpected type {}", key, value.getClass()
257 throw new RuntimeException(
258 "Found variable of unexpected type: " + key
264 LOG.info("adding new variable {}", key);
265 handler.setLocalVariable(key, value);
272 Entry<String, Object> variable = it.next();
273 String key = variable.getKey();
274 Object value = variable.getValue();
275 LOG.info("adding variable {}", key);
276 variables.put(key, value);
277 handler.setLocalVariable(key, value);
280 catch (IllegalArgumentException e)
282 LOG.error("cannot parse {} as JSON: {}", location, e.getMessage());
283 throw new RuntimeException(e);
286 handler.removeAttribute(name);
290 Map<String, Object> getVariables(ITemplateContext context)
292 Map<String, Object> variables = new HashMap<>();
297 static Object convert(JsonParser parser) throws IOException
299 JsonToken token = parser.getCurrentToken();
301 fail(parser, "unexpected EOF");
305 case VALUE_STRING: return parser.getText();
306 case VALUE_NUMBER_INT: return parser.getIntValue();
307 case VALUE_NUMBER_FLOAT: return parser.getDoubleValue();
308 case START_OBJECT: return convertObject(parser);
309 case START_ARRAY: return convertArray(parser);
310 case VALUE_TRUE: return Boolean.TRUE;
311 case VALUE_FALSE: return Boolean.FALSE;
312 case VALUE_NULL: return null;
315 fail(parser, "unexpected token " + token.toString());
316 return null; // << Will never be reached, because fail always throws an exception
319 static Map<String, Object> convertObject(JsonParser parser) throws IOException
321 JsonToken token = parser.nextToken();
323 fail(parser, "unexpected EOF");
325 Map<String, Object> map = new LinkedHashMap<>();
327 while (!JsonToken.END_OBJECT.equals(token))
329 if (!JsonToken.FIELD_NAME.equals(token))
330 fail(parser, "expected a field-name");
332 String name = parser.getText();
334 Object value = convert(parser);
335 map.put(name, value);
337 token = parser.nextToken();
339 fail(parser, "unexpected EOF");
345 static List<Object> convertArray(JsonParser parser) throws IOException
347 JsonToken token = parser.nextToken();
349 fail(parser, "unexpected EOF");
351 List<Object> list = new LinkedList<>();
353 while (!JsonToken.END_ARRAY.equals(token))
355 list.add(convert(parser));
357 token = parser.nextToken();
359 fail(parser, "unexpected EOF");
365 static void fail(JsonParser parser, String message)
367 JsonLocation location = parser.getCurrentLocation();
369 "{} at char-offset {} (line {}, column {})",
371 location.getCharOffset(),
372 location.getLineNr(),
373 location.getColumnNr()
375 throw new RuntimeException("Cannot parse JSON: " + message);