import de.juplo.jackson.SimpleMapper;
import java.io.IOException;
import java.io.InputStream;
+import java.util.HashMap;
import java.util.Iterator;
+import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.thymeleaf.Arguments;
import org.thymeleaf.context.IContext;
import org.thymeleaf.context.VariablesMap;
import org.thymeleaf.dom.Element;
+import org.thymeleaf.dom.Node;
import org.thymeleaf.processor.ProcessorResult;
import org.thymeleaf.processor.attr.AbstractAttrProcessor;
import org.thymeleaf.resourceresolver.IResourceResolver;
*/
public class ImportVariablesAttrProcessor extends AbstractAttrProcessor
{
- public static final int ATTR_PRECEDENCE = 200;
-
private static final Logger LOG =
LoggerFactory.getLogger(ImportVariablesAttrProcessor.class);
private static final JsonFactory FACTORY = new JsonFactory();
+ private static final String PROPERTY_NAME =
+ ImportVariablesAttrProcessor.class.getCanonicalName() + "_VARIABLES";
+
+ public static final Pattern PATTERN =
+ Pattern.compile(
+ "^\\s*(?:(?:(merge)|replace):)?\\s*(?:(\\{.*\\})|(.*))\\s*$",
+ Pattern.DOTALL | Pattern.CASE_INSENSITIVE
+ );
+ public static final int ATTR_PRECEDENCE = 200;
public ImportVariablesAttrProcessor()
)
{
Configuration config = arguments.getConfiguration();
- String templateName = element.getAttributeValue(name);
+ String parameter = element.getAttributeValue(name);
- TemplateProcessingParameters params =
- new TemplateProcessingParameters(
- config,
- templateName,
- new IContext() // << We will not execute the template, hence we need no context
- {
- @Override
- public VariablesMap<String, Object> getVariables()
- {
- return new VariablesMap<>();
- }
+ Iterator<Entry<String, Object>> it = null;
- @Override
- public Locale getLocale()
- {
- return Locale.getDefault();
- }
+ Matcher matcher = PATTERN.matcher(parameter);
+ boolean merge = false;
+ String json = null;
+ String resource = parameter;
- @Override
- public void addContextExecutionInfo(String templateName)
- {
- }
- });
-
- for (ITemplateResolver t_resolver : config.getTemplateResolvers())
+ if (matcher.matches())
{
- 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, resolution.getResourceName());
- if (is == null)
- continue;
+ merge = matcher.group(1) != null;
+ json = matcher.group(2);
+ resource = matcher.group(3);
+ }
+ if (json != null)
+ {
+ LOG.info("parsing parameter as JSON");
+ LOG.debug("parameter: {}", json);
try
{
- Iterator<Entry<String, Object>> it =
- SimpleMapper.getObjectIterator(FACTORY.createParser(is));
- while(it.hasNext())
- {
- Entry<String, Object> entry = it.next();
- element.setNodeLocalVariable(entry.getKey(), entry.getValue());
- }
+ it = SimpleMapper.getObjectIterator(FACTORY.createParser(json));
}
catch (IOException e)
{
- LOG.error("cannot retreive {} as JSON: {}", templateName, e.getMessage());
+ LOG.error("cannot parse parameter as JSON: {}", json, e.getMessage());
throw new RuntimeException(e);
}
- catch (IllegalArgumentException e)
+ }
+ else
+ {
+ LOG.info("retriving {} as JSON-template", resource);
+ TemplateProcessingParameters params =
+ new TemplateProcessingParameters(
+ config,
+ resource,
+ new IContext() // << We will not execute the template, hence we need no context
+ {
+ @Override
+ public VariablesMap<String, Object> getVariables()
+ {
+ return new VariablesMap<>();
+ }
+
+ @Override
+ public Locale getLocale()
+ {
+ return Locale.getDefault();
+ }
+
+ @Override
+ public void addContextExecutionInfo(String templateName)
+ {
+ }
+ });
+
+ for (ITemplateResolver t_resolver : config.getTemplateResolvers())
{
- LOG.error("cannot parse {} as JSON: {}", templateName, e.getMessage());
- throw new RuntimeException(e);
+ 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, resolution.getResourceName());
+ if (is == null)
+ continue;
+
+ try
+ {
+ it = SimpleMapper.getObjectIterator(FACTORY.createParser(is));
+ break;
+ }
+ catch (IOException | IllegalArgumentException e)
+ {
+ LOG.error("cannot retreive {} as JSON: {}", parameter, e.getMessage());
+ throw new RuntimeException(e);
+ }
+ }
+
+ if (it == null)
+ {
+ LOG.error("cannot resolve {} as JSON (not found)!", parameter);
+ throw new RuntimeException("Template not found: " + parameter);
}
}
+ try
+ {
+ Map<String, Object> variables = getVariables(element);
+ if (merge)
+ {
+ while(it.hasNext())
+ {
+ Entry<String, Object> variable = it.next();
+ String key = variable.getKey();
+ Object value = variable.getValue();
+ Object existing = variables.get(key);
+ if (existing != null)
+ {
+ if (value instanceof String)
+ {
+ if (!(existing instanceof String))
+ {
+ LOG.error(
+ "cannot merge variable {} of type {} with a string",
+ key,
+ existing.getClass()
+ );
+ throw new RuntimeException(
+ "Type-Missmatch for variable " + key
+ );
+ }
+
+ String string = ((String)existing).concat((String) value);
+ LOG.info("appending variable to string {}", key);
+ element.setNodeLocalVariable(key, string);
+ }
+ else if (value instanceof Map)
+ {
+ if (!(existing instanceof Map))
+ {
+ LOG.error(
+ "cannot merge variable {} of type {} with a map",
+ key,
+ existing.getClass()
+ );
+ throw new RuntimeException(
+ "Type-Missmatch for variable " + key
+ );
+ }
+
+ Map map = ((Map)existing);
+ map.putAll((Map) value);
+ LOG.info("merging variable with map {}", key);
+ element.setNodeLocalVariable(key, map);
+ }
+ else if (value instanceof List)
+ {
+ if (!(existing instanceof List))
+ {
+ LOG.error(
+ "cannot merge variable {} of type {} with a list",
+ key,
+ existing.getClass()
+ );
+ throw new RuntimeException(
+ "Type-Missmatch for variable " + key
+ );
+ }
+
+ List list = ((List)existing);
+ list.addAll((List) value);
+ LOG.info("appending contents of variable to list {}", key);
+ element.setNodeLocalVariable(key, list);
+ }
+ else
+ {
+ LOG.error(
+ "variable {} is of unexpected type {}", key, value.getClass()
+ );
+ throw new RuntimeException(
+ "Found variable of unexpected type: " + key
+ );
+ }
+ }
+ else
+ {
+ LOG.info("adding new variable {}", key);
+ element.setNodeLocalVariable(key, value);
+ }
+ }
+ }
+ else
+ while(it.hasNext())
+ {
+ Entry<String, Object> variable = it.next();
+ String key = variable.getKey();
+ Object value = variable.getValue();
+ LOG.info("adding variable {}", key);
+ variables.put(key, value);
+ element.setNodeLocalVariable(key, value);
+ }
+ }
+ catch (IllegalArgumentException e)
+ {
+ LOG.error("cannot parse {} as JSON: {}", parameter, e.getMessage());
+ throw new RuntimeException(e);
+ }
+
element.removeAttribute(name);
return ProcessorResult.OK;
}
+ Map<String, Object> getVariables(Node node)
+ {
+ Node parent = node;
+ do
+ {
+ Map<String, Object> variables =
+ (Map<String, Object>)parent.getNodeProperty(PROPERTY_NAME);
+
+ if (variables != null)
+ return variables;
+
+ parent = parent.getParent();
+ }
+ while (parent != null);
+
+ Map<String, Object> variables = new HashMap<>();
+ node.setNodeProperty(PROPERTY_NAME, variables);
+ return variables;
+ }
+
+
@Override
public int getPrecedence()
{