WIP:Ported app to Thymeleaf 3.0.x - Überflüssiger code (simple-mapper)
[juplo-dialect] / src / main / java / de / juplo / thymeleaf / ImportVariablesAttributeProcessor.java
1 package de.juplo.thymeleaf;
2
3
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.List;
10 import java.util.Map;
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.IEngineConfiguration;
17 import org.thymeleaf.context.ITemplateContext;
18 import org.thymeleaf.engine.AttributeName;
19 import org.thymeleaf.engine.EngineEventUtils;
20 import org.thymeleaf.exceptions.TemplateProcessingException;
21 import org.thymeleaf.model.IProcessableElementTag;
22 import org.thymeleaf.processor.element.AbstractAttributeTagProcessor;
23 import org.thymeleaf.processor.element.IElementTagStructureHandler;
24 import org.thymeleaf.standard.expression.FragmentExpression;
25 import org.thymeleaf.standard.expression.FragmentExpression.ExecutedFragmentExpression;
26 import org.thymeleaf.standard.expression.IStandardExpression;
27 import org.thymeleaf.standard.expression.NoOpToken;
28 import org.thymeleaf.standard.expression.StandardExpressionExecutionContext;
29 import org.thymeleaf.templatemode.TemplateMode;
30 import org.thymeleaf.templateresolver.ITemplateResolver;
31 import org.thymeleaf.templateresolver.TemplateResolution;
32
33
34
35 /**
36  * Retrievs and parses JSON-data and imports the parsed variables as node-local
37  * variables.
38  * @author Kai Moritz
39  */
40 public class ImportVariablesAttributeProcessor extends AbstractAttributeTagProcessor
41 {
42   private static final Logger LOG =
43       LoggerFactory.getLogger(ImportVariablesAttributeProcessor.class);
44   private static final JsonFactory FACTORY = new JsonFactory();
45   private static final String PROPERTY_NAME =
46       ImportVariablesAttributeProcessor.class.getCanonicalName() + "_VARIABLES";
47
48   public static final Pattern PATTERN =
49       Pattern.compile(
50           "^\\s*(?:(?:(merge)|replace):)?\\s*(?:(\\{.*\\})|(.*))\\s*$",
51           Pattern.DOTALL | Pattern.CASE_INSENSITIVE
52           );
53
54   public static final String ATTR_NAME = "variables";
55   public static final int ATTR_PRECEDENCE = 200;
56
57
58   public ImportVariablesAttributeProcessor(String prefix)
59   {
60     super(TemplateMode.HTML, prefix, null, false, ATTR_NAME, true, ATTR_PRECEDENCE, true);
61   }
62
63
64   @Override
65   protected void doProcess(
66       final ITemplateContext context,
67       final IProcessableElementTag element,
68       final AttributeName name,
69       final String attribute,
70       final IElementTagStructureHandler handler
71       )
72   {
73     if (attribute == null)
74       return;
75
76     String location;
77     try
78     {
79       final Object result;
80       final IStandardExpression expression =
81          EngineEventUtils.computeAttributeExpression(
82              context,
83              element,
84              name,
85              attribute
86              );
87
88       if (expression != null && expression instanceof FragmentExpression)
89       {
90         final ExecutedFragmentExpression executedFragmentExpression =
91             FragmentExpression.createExecutedFragmentExpression(
92                 context,
93                 (FragmentExpression) expression,
94                 StandardExpressionExecutionContext.NORMAL
95                 );
96         result =
97             FragmentExpression.resolveExecutedFragmentExpression(
98                 context,
99                 executedFragmentExpression,
100                 true
101                 );
102       }
103       else
104       {
105         result = expression.execute(context);
106       }
107
108       // If the result of this expression is NO-OP, there is nothing to execute
109       if (result == NoOpToken.VALUE)
110       {
111         handler.removeAttribute(name);
112         return;
113       }
114
115       location = result.toString();
116     }
117     catch (final TemplateProcessingException e)
118     {
119       location = attribute;
120     }
121
122
123     Iterator<Entry<String, Object>> it = null;
124
125     Matcher matcher = PATTERN.matcher(location);
126     boolean merge = false;
127     String json = null;
128
129     if (matcher.matches())
130     {
131       merge = matcher.group(1) != null;
132       json = matcher.group(2);
133       location = matcher.group(3);
134     }
135
136     if (json != null)
137     {
138       LOG.info("parsing parameter as JSON");
139       LOG.debug("parameter: {}", json);
140       try
141       {
142         it = SimpleMapper.getObjectIterator(FACTORY.createParser(json));
143       }
144       catch (IOException e)
145       {
146         LOG.error("cannot parse parameter as JSON: {}", json, e.getMessage());
147         return;
148       }
149     }
150     else
151     {
152       LOG.info("retriving {} as Spring-resource", location);
153       IEngineConfiguration config = context.getConfiguration();
154       for (ITemplateResolver resolver : config.getTemplateResolvers())
155       {
156         TemplateResolution resolution =
157             resolver.resolveTemplate(
158                 config,
159                 context.getTemplateData().getTemplate(),
160                 location,
161                 null
162                 );
163         if (resolution != null)
164         {
165           try
166           {
167             it = SimpleMapper.getObjectIterator(FACTORY.createParser(resolution.getTemplateResource().reader()));
168             break;
169           }
170           catch (IOException e) {}
171         }
172       }
173
174       if (it == null)
175       {
176         LOG.error("cannot resolve {} as JSON (not found)!", location);
177         return;
178       }
179     }
180
181     try
182     {
183       Map<String, Object> variables = getVariables(context);
184       if (merge)
185       {
186         while(it.hasNext())
187         {
188           Entry<String, Object> variable = it.next();
189           String key = variable.getKey();
190           Object value = variable.getValue();
191           Object existing = context.getVariable(key);
192           if (existing != null)
193           {
194             if (value instanceof String)
195             {
196               if (!(existing instanceof String))
197               {
198                 LOG.error(
199                     "cannot merge variable {} of type {} with a string",
200                     key,
201                     existing.getClass()
202                     );
203                 throw new RuntimeException(
204                     "Type-Missmatch for variable  " + key
205                     );
206               }
207
208               String string = ((String)existing).concat((String) value);
209               LOG.info("appending variable to string {}", key);
210               handler.setLocalVariable(key, string);
211             }
212             else if (value instanceof Map)
213             {
214               if (!(existing instanceof Map))
215               {
216                 LOG.error(
217                     "cannot merge variable {} of type {} with a map",
218                     key,
219                     existing.getClass()
220                     );
221                 throw new RuntimeException(
222                     "Type-Missmatch for variable  " + key
223                     );
224               }
225
226               Map map = ((Map)existing);
227               map.putAll((Map) value);
228               LOG.info("merging variable with map {}", key);
229               handler.setLocalVariable(key, map);
230             }
231             else if (value instanceof List)
232             {
233               if (!(existing instanceof List))
234               {
235                 LOG.error(
236                     "cannot merge variable {} of type {} with a list",
237                     key,
238                     existing.getClass()
239                     );
240                 throw new RuntimeException(
241                     "Type-Missmatch for variable  " + key
242                     );
243               }
244
245               List list = ((List)existing);
246               list.addAll((List) value);
247               LOG.info("appending contents of variable to list {}", key);
248               handler.setLocalVariable(key, list);
249             }
250             else
251             {
252               LOG.error(
253                   "variable {} is of unexpected type {}", key, value.getClass()
254                   );
255               throw new RuntimeException(
256                   "Found variable of unexpected type: " + key
257                   );
258             }
259           }
260           else
261           {
262             LOG.info("adding new variable {}", key);
263             handler.setLocalVariable(key, value);
264           }
265         }
266       }
267       else
268         while(it.hasNext())
269         {
270           Entry<String, Object> variable = it.next();
271           String key = variable.getKey();
272           Object value = variable.getValue();
273           LOG.info("adding variable {}", key);
274           variables.put(key, value);
275           handler.setLocalVariable(key, value);
276         }
277     }
278     catch (IllegalArgumentException e)
279     {
280       LOG.error("cannot parse {} as JSON: {}", location, e.getMessage());
281       throw new RuntimeException(e);
282     }
283
284     handler.removeAttribute(name);
285   }
286
287
288   Map<String, Object> getVariables(ITemplateContext context)
289   {
290     Map<String, Object> variables = new HashMap<>();
291     return variables;
292   }
293 }