import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonLocation;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
-import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.InputStream;
import java.time.format.DateTimeFormatter;
-import java.util.Locale;
-import java.util.Map.Entry;
-import java.util.logging.Level;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Configurable;
-import org.springframework.context.MessageSource;
-import org.thymeleaf.Arguments;
-import org.thymeleaf.Configuration;
-import org.thymeleaf.TemplateProcessingParameters;
-import org.thymeleaf.context.IContext;
-import org.thymeleaf.dom.Element;
-import org.thymeleaf.processor.ProcessorResult;
-import org.thymeleaf.processor.attr.AbstractAttrProcessor;
-import org.thymeleaf.resourceresolver.IResourceResolver;
-import org.thymeleaf.standard.expression.IStandardExpression;
-import org.thymeleaf.templateresolver.ITemplateResolver;
-import org.thymeleaf.templateresolver.TemplateResolution;
-import org.thymeleaf.util.StringUtils;
*
* @author kai
*/
-@Configurable
-public class ImportVariablesAttrProcessor extends AbstractAttrProcessor
+public class ImportVariablesAttrProcessor
{
- public static final int ATTR_PRECEDENCE = 200;
- public static final String ATTR_VAR_NAME =
- JuploDialect.DIALECT_PREFIX + ":var";
- public static final String ATTR_LOCALE_NAME =
- JuploDialect.DIALECT_PREFIX + ":locale";
- public static final String DEFAULT_VAR_NAME = "crumb";
-
private static final Logger LOG =
LoggerFactory.getLogger(ImportVariablesAttrProcessor.class);
private static final DateTimeFormatter FORMATTER =
private static final JsonFactory FACTORY = new JsonFactory();
- @Autowired
- MessageSource messageSource;
- @Autowired
- Locale defaultLocale;
-
-
- public ImportVariablesAttrProcessor()
+ public Object processAttribute(InputStream is)
{
- super("crumb");
- }
-
-
- @Override
- public final ProcessorResult processAttribute(
- final Arguments arguments,
- final Element element,
- final String name
- )
- {
- Configuration config = arguments.getConfiguration();
- String templateName = element.getAttributeValue(name);
-
- TemplateProcessingParameters params =
- new TemplateProcessingParameters(
- config,
- templateName,
- (IContext)null // << We will not execute the template, hence we need no context
- );
-
- for (ITemplateResolver t_resolver : config.getTemplateResolvers())
- {
- TemplateResolution resolution = t_resolver.resolveTemplate(params);
- if (resolution == null)
- continue;
- if (!"JSON".equals(resolution.getTemplateMode()))
- continue;
- IResourceResolver r_resolver = resolution.getResourceResolver();
- InputStream is = r_resolver.getResourceAsStream(params, templateName);
- if (is == null)
- continue;
-
try
{
/** Read the JSON and create the variables */
JsonParser parser = FACTORY.createParser(is);
- JsonToken root = parser.nextToken();
+ JsonToken token = parser.nextToken();
-
- JsonNode root = MAPPER.readTree(is);
-
- if (root == null)
+ if (token == null)
{
- LOG.warn("found empty content for {}", templateName);
- break;
+ LOG.warn("empty input-stream");
+ return null;
}
- if (!root.isObject())
- {
- LOG.error("{} must contain an object as root-element", templateName);
- throw new RuntimeException(
- "The root-element of " +
- templateName +
- " has to be an object, that contains the variable-definitions!"
- );
- }
-root.
- for (Entry<String, JsonNode> entry : root.fields())
- {
- Object var = ImportVariablesAttrProcessor.convert(root.get(i));
- element.setNodeLocalVariable(name, var);
- }
+ Object result = convert(parser);
+
+ if (parser.nextToken() != null)
+ fail(parser, "unexpected data after parsed variables");
+
+ return result;
}
catch (IOException e)
{
- LOG.error("cannot parse {} as JSON: {}", templateName, e.getMessage());
+ LOG.error("cannot parse input-stream as JSON: {}", e.getMessage());
throw new RuntimeException(e);
}
- }
+ }
- element.removeAttribute(name);
- return ProcessorResult.OK;
- }
+ static Object convert(JsonParser parser) throws IOException
+ {
+ JsonToken token = parser.getCurrentToken();
+ if (token == null)
+ fail(parser, "unexpected EOF");
+
+ switch (token)
+ {
+ case VALUE_STRING: return parser.getText();
+ case VALUE_NUMBER_INT: return parser.getIntValue();
+ case VALUE_NUMBER_FLOAT: return parser.getDoubleValue();
+ case START_OBJECT: return convertObject(parser);
+ case START_ARRAY: return convertArray(parser);
+ case VALUE_TRUE: return Boolean.TRUE;
+ case VALUE_FALSE: return Boolean.FALSE;
+ case VALUE_NULL: return null;
+ }
+ fail(parser, "unexpected token " + token.toString());
+ return null; // << Will never be reached, because fail always throws an exception
+ }
- @Override
- public int getPrecedence()
+ static Map<String, Object> convertObject(JsonParser parser) throws IOException
{
- return ATTR_PRECEDENCE;
+ JsonToken token = parser.nextToken();
+ if (token == null)
+ fail(parser, "unexpected EOF");
+
+ Map<String, Object> map = new LinkedHashMap<>();
+
+ while (!JsonToken.END_OBJECT.equals(token))
+ {
+ if (!JsonToken.FIELD_NAME.equals(token))
+ fail(parser, "expected a field-name");
+
+ String name = parser.getText();
+ parser.nextToken();
+ Object value = convert(parser);
+ map.put(name, value);
+
+ token = parser.nextToken();
+ if (token == null)
+ fail(parser, "unexpected EOF");
+ }
+
+ return map;
}
+ static List<Object> convertArray(JsonParser parser) throws IOException
+ {
+ JsonToken token = parser.nextToken();
+ if (token == null)
+ fail(parser, "unexpected EOF");
+
+ List<Object> list = new LinkedList<>();
+
+ while (!JsonToken.END_ARRAY.equals(token))
+ {
+ list.add(convert(parser));
+
+ token = parser.nextToken();
+ if (token == null)
+ fail(parser, "unexpected EOF");
+ }
+
+ return list;
+ }
- public static Object convert(JsonNode node)
+ static void fail(JsonParser parser, String message)
{
- return null;
+ JsonLocation location = parser.getCurrentLocation();
+ LOG.error(
+ "{} at char-offset {} (line {}, column {})",
+ message,
+ location.getCharOffset(),
+ location.getLineNr(),
+ location.getColumnNr()
+ );
+ throw new RuntimeException("Cannot parse JSON: " + message);
}
}