1 package de.juplo.thymeleaf;
4 import com.fasterxml.jackson.core.JsonFactory;
5 import com.fasterxml.jackson.core.JsonLocation;
6 import com.fasterxml.jackson.core.JsonParser;
7 import com.fasterxml.jackson.core.JsonToken;
8 import com.fasterxml.jackson.databind.ObjectMapper;
9 import java.io.IOException;
10 import java.io.InputStream;
11 import java.time.format.DateTimeFormatter;
12 import java.util.HashMap;
13 import java.util.LinkedList;
14 import java.util.List;
15 import java.util.Locale;
17 import org.slf4j.Logger;
18 import org.slf4j.LoggerFactory;
19 import org.springframework.beans.factory.annotation.Autowired;
20 import org.springframework.beans.factory.annotation.Configurable;
21 import org.springframework.context.MessageSource;
22 import org.thymeleaf.Arguments;
23 import org.thymeleaf.Configuration;
24 import org.thymeleaf.TemplateProcessingParameters;
25 import org.thymeleaf.context.IContext;
26 import org.thymeleaf.dom.Element;
27 import org.thymeleaf.processor.ProcessorResult;
28 import org.thymeleaf.processor.attr.AbstractAttrProcessor;
29 import org.thymeleaf.resourceresolver.IResourceResolver;
30 import org.thymeleaf.templateresolver.ITemplateResolver;
31 import org.thymeleaf.templateresolver.TemplateResolution;
40 public class ImportVariablesAttrProcessor extends AbstractAttrProcessor
42 public static final int ATTR_PRECEDENCE = 200;
43 public static final String ATTR_VAR_NAME =
44 JuploDialect.DIALECT_PREFIX + ":var";
45 public static final String ATTR_LOCALE_NAME =
46 JuploDialect.DIALECT_PREFIX + ":locale";
47 public static final String DEFAULT_VAR_NAME = "crumb";
49 private static final Logger LOG =
50 LoggerFactory.getLogger(ImportVariablesAttrProcessor.class);
51 private static final DateTimeFormatter FORMATTER =
52 DateTimeFormatter.ofPattern("dd.MM");
53 private static final ObjectMapper MAPPER = new ObjectMapper();
54 private static final JsonFactory FACTORY = new JsonFactory();
58 MessageSource messageSource;
63 public ImportVariablesAttrProcessor()
70 public final ProcessorResult processAttribute(
71 final Arguments arguments,
72 final Element element,
76 Configuration config = arguments.getConfiguration();
77 String templateName = element.getAttributeValue(name);
79 TemplateProcessingParameters params =
80 new TemplateProcessingParameters(
83 (IContext)null // << We will not execute the template, hence we need no context
86 for (ITemplateResolver t_resolver : config.getTemplateResolvers())
88 TemplateResolution resolution = t_resolver.resolveTemplate(params);
89 if (resolution == null)
91 if (!"JSON".equals(resolution.getTemplateMode()))
93 IResourceResolver r_resolver = resolution.getResourceResolver();
94 InputStream is = r_resolver.getResourceAsStream(params, templateName);
100 /** Read the JSON and create the variables */
101 JsonParser parser = FACTORY.createParser(is);
103 JsonToken token = parser.nextToken();
107 LOG.warn("found empty content for {}", templateName);
111 if (!JsonToken.START_OBJECT.equals(token))
113 LOG.error("{} must contain an object as root-element", templateName);
114 throw new RuntimeException(
115 "The root-element of " +
117 " has to be an object, that contains the variable-definitions!"
121 token = parser.nextToken();
123 fail(parser, "unexpected EOF");
124 if (!JsonToken.END_OBJECT.equals(token))
126 LOG.warn("found empty object for {}", templateName);
132 if (!JsonToken.FIELD_NAME.equals(token))
133 fail(parser, "expected a field-name");
135 String var_name = parser.getText();
136 Object var_value = convert(parser);
139 "defining variable {} of type {}",
141 var_value == null ? "NULL" : var_value.getClass().getSimpleName()
143 element.setNodeLocalVariable(var_name, var_value);
145 token = parser.nextToken();
147 fail(parser, "unexpected EOF");
149 while (!JsonToken.END_OBJECT.equals(token));
151 if (parser.nextToken() != null)
152 fail(parser, "unexpected data after parsed variables");
154 catch (IOException e)
156 LOG.error("cannot parse {} as JSON: {}", templateName, e.getMessage());
157 throw new RuntimeException(e);
161 element.removeAttribute(name);
163 return ProcessorResult.OK;
168 public int getPrecedence()
170 return ATTR_PRECEDENCE;
174 static Object convert(JsonParser parser) throws IOException
176 JsonToken token = parser.nextToken();
178 fail(parser, "unexpected EOF");
182 case VALUE_STRING: return parser.getText();
183 case VALUE_NUMBER_INT: return parser.getIntValue();
184 case VALUE_NUMBER_FLOAT: return parser.getDoubleValue();
185 case START_OBJECT: return convertObject(parser);
186 case START_ARRAY: return convertArray(parser);
187 case VALUE_TRUE: return Boolean.TRUE;
188 case VALUE_FALSE: return Boolean.FALSE;
189 case VALUE_NULL: return null;
192 fail(parser, "unexpected token " + token.toString());
193 return null; // << Will never be reached, because fail always throws an exception
196 static Map<String, Object> convertObject(JsonParser parser) throws IOException
198 JsonToken token = parser.nextToken();
200 fail(parser, "unexpected EOF");
202 Map<String, Object> map = new HashMap<>();
204 while (!JsonToken.END_OBJECT.equals(token))
206 if (!JsonToken.FIELD_NAME.equals(token))
207 fail(parser, "expected a field-name");
209 map.put(parser.getText(), convert(parser));
211 token = parser.nextToken();
213 fail(parser, "unexpected EOF");
219 static List<Object> convertArray(JsonParser parser) throws IOException
221 JsonToken token = parser.nextToken();
223 fail(parser, "unexpected EOF");
225 List<Object> list = new LinkedList<>();
227 while (!JsonToken.END_ARRAY.equals(token))
229 list.add(convert(parser));
231 token = parser.nextToken();
233 fail(parser, "unexpected EOF");
239 static void fail(JsonParser parser, String message)
241 JsonLocation location = parser.getCurrentLocation();
243 "{} at char-offset {} (line {}, column {})",
245 location.getCharOffset(),
246 location.getLineNr(),
247 location.getColumnNr()
249 throw new RuntimeException("Cannot parse JSON: " + message);