1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.rendering.renderer;
35
36 import info.magnolia.cms.core.AggregationState;
37 import info.magnolia.context.MgnlContext;
38 import info.magnolia.jcr.decoration.ContentDecoratorUtil;
39 import info.magnolia.jcr.util.ContentMap;
40 import info.magnolia.jcr.util.NodeUtil;
41 import info.magnolia.jcr.wrapper.ChannelVisibilityContentDecorator;
42 import info.magnolia.jcr.wrapper.HTMLEscapingNodeWrapper;
43 import info.magnolia.jcr.wrapper.I18nNodeWrapper;
44 import info.magnolia.objectfactory.Components;
45 import info.magnolia.objectfactory.MgnlInstantiationException;
46 import info.magnolia.objectfactory.ParameterInfo;
47 import info.magnolia.objectfactory.ParameterResolver;
48 import info.magnolia.rendering.context.RenderingContext;
49 import info.magnolia.rendering.engine.RenderException;
50 import info.magnolia.rendering.engine.RenderingEngine;
51 import info.magnolia.rendering.model.EarlyExecutionAware;
52 import info.magnolia.rendering.model.ModelExecutionFilter;
53 import info.magnolia.rendering.model.RenderingModel;
54 import info.magnolia.rendering.model.RenderingModelImpl;
55 import info.magnolia.rendering.template.RenderableDefinition;
56
57 import java.lang.reflect.InvocationTargetException;
58 import java.util.HashMap;
59 import java.util.Map;
60 import java.util.Map.Entry;
61
62 import javax.inject.Inject;
63 import javax.jcr.Node;
64 import javax.jcr.RepositoryException;
65
66 import org.apache.commons.beanutils.BeanUtils;
67 import org.apache.commons.lang.StringUtils;
68 import org.apache.commons.lang.exception.ExceptionUtils;
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83 public abstract class AbstractRenderer implements Renderer, RenderingModelBasedRenderer {
84
85 protected static final String MODEL_ATTRIBUTE = RenderingModel.class.getName();
86
87 private Map<String, ContextAttributeConfiguration> contextAttributes = new HashMap<String, ContextAttributeConfiguration>();
88
89 private RenderingEngine renderingEngine = null;
90
91
92
93
94 public AbstractRenderer() {
95 this.renderingEngine = Components.getComponent(RenderingEngine.class);
96 }
97
98 @Inject
99 public AbstractRenderer(RenderingEngine renderingEngine) {
100 this.renderingEngine = renderingEngine;
101 }
102
103 @Override
104 public void render(RenderingContext renderingCtx, Map<String, Object> contextObjects) throws RenderException {
105
106 final RenderingModel<?> parentModel = MgnlContext.getAttribute(MODEL_ATTRIBUTE);
107 Node content = renderingCtx.getCurrentContent();
108 RenderableDefinition definition = renderingCtx.getRenderableDefinition();
109
110 RenderingModel<?> model = null;
111 String actionResult = null;
112
113 if (content != null) {
114 String uuid;
115 try {
116 uuid = content.getIdentifier();
117 } catch (RepositoryException e) {
118 throw new RenderException(e);
119 }
120
121 model = MgnlContext.getAttribute(ModelExecutionFilter.MODEL_ATTRIBUTE_PREFIX + uuid);
122 if (model != null) {
123 actionResult = (String) MgnlContext.getAttribute(ModelExecutionFilter.ACTION_RESULT_ATTRIBUTE_PREFIX + uuid);
124 if (model instanceof EarlyExecutionAware) {
125 ((EarlyExecutionAware) model).setParent(parentModel);
126 }
127 }
128 }
129
130 if (model == null) {
131 model = newModel(content, definition, parentModel);
132 if (model != null) {
133 actionResult = model.execute();
134 if (RenderingModel.SKIP_RENDERING.equals(actionResult)) {
135 return;
136 }
137 }
138 }
139
140 String templatePath = resolveTemplateScript(content, definition, model, actionResult);
141 if (templatePath == null) {
142 throw new RenderException("No template script defined for the template definition [" + definition + "]");
143 }
144
145 final Map<String, Object> ctx = newContext();
146 final Map<String, Object> savedContextState = saveContextState(ctx);
147 setupContext(ctx, content, definition, model, actionResult);
148 ctx.putAll(contextObjects);
149 MgnlContext.setAttribute(MODEL_ATTRIBUTE, model);
150 content = wrapNodeForModel(content);
151 renderingCtx.push(content, definition);
152 try {
153 onRender(content, definition, renderingCtx, ctx, templatePath);
154 } finally {
155 renderingCtx.pop();
156 }
157 MgnlContext.setAttribute(MODEL_ATTRIBUTE, parentModel);
158
159 restoreContext(ctx, savedContextState);
160 }
161
162
163
164
165
166
167
168 protected String resolveTemplateScript(Node content, RenderableDefinition definition, RenderingModel<?> model, final String actionResult) {
169 return definition.getTemplateScript();
170 }
171
172
173
174
175
176 @Override
177 public RenderingModel<?> newModel(final Node content, final RenderableDefinition definition, final RenderingModel<?> parentModel) throws RenderException {
178
179 Class clazz = definition.getModelClass();
180
181
182 if (clazz == null) {
183 clazz = RenderingModelImpl.class;
184 }
185
186 final Node wrappedContent = wrapNodeForModel(content);
187
188 return newModel(clazz, wrappedContent, definition, parentModel);
189 }
190
191 protected <T extends RenderingModel<?>> T newModel(Class<T> modelClass, final Node content, final RenderableDefinition definition, final RenderingModel<?> parentModel) throws RenderException {
192
193 try {
194
195 T model = Components.getComponentProvider().newInstanceWithParameterResolvers(modelClass,
196 new ParameterResolver() {
197 @Override
198 public Object resolveParameter(ParameterInfo parameter) {
199 if (parameter.getParameterType().equals(Node.class)) {
200 return content;
201 }
202 if (parameter.getParameterType().isAssignableFrom(definition.getClass())) {
203 return definition;
204 }
205 if (parameter.getParameterType().equals(RenderingModel.class)) {
206 return parentModel;
207 }
208 return UNRESOLVED;
209 }
210 }
211 );
212
213
214 final boolean autoPopulateFromRequest = definition.getAutoPopulateFromRequest() != null ? definition.getAutoPopulateFromRequest() : renderingEngine.getAutoPopulateFromRequest();
215 if (!autoPopulateFromRequest) {
216 return model;
217 }
218
219 Map<String, String[]> params = MgnlContext.getWebContext().getRequest().getParameterMap();
220 if (params == null || params.isEmpty()) {
221 return model;
222 }
223
224
225
226 Map<String, Object> filtered = new HashMap<String, Object>();
227 for (Entry<String, String[]> entry : params.entrySet()) {
228 String key = entry.getKey();
229 String[] value = entry.getValue();
230 if (StringUtils.contains(key, "[")) {
231 key = StringUtils.substringBefore(key, "[");
232 }
233 filtered.put(key, value);
234 }
235
236 BeanUtils.populate(model, filtered);
237
238 return model;
239
240 } catch (MgnlInstantiationException e) {
241 throw new RenderException("Can't instantiate model: " + modelClass, e);
242 } catch (InvocationTargetException e) {
243 throw new RenderException("Can't populate rendering model: " + ExceptionUtils.getRootCauseMessage(e), e);
244 } catch (IllegalAccessException e) {
245 throw new RenderException("Can't populate rendering model: " + ExceptionUtils.getRootCauseMessage(e), e);
246 }
247 }
248
249 protected Map<String, Object> saveContextState(final Map<String, Object> ctx) {
250 Map<String, Object> state = new HashMap<String, Object>();
251
252 saveAttribute(ctx, state, "content");
253 saveAttribute(ctx, state, "def");
254 saveAttribute(ctx, state, "state");
255 saveAttribute(ctx, state, "model");
256 saveAttribute(ctx, state, "actionResult");
257
258 return state;
259 }
260
261 protected void saveAttribute(final Map<String, Object> ctx, Map<String, Object> state, String name) {
262 state.put(name, ctx.get(name));
263 }
264
265 protected void restoreContext(final Map<String, Object> ctx, Map<String, Object> state) {
266 for (Entry<String, Object> entry : state.entrySet()) {
267 setContextAttribute(ctx, entry.getKey(), entry.getValue());
268 }
269 }
270
271 protected void setupContext(final Map<String, Object> ctx, Node content, RenderableDefinition definition, RenderingModel<?> model, Object actionResult) {
272 final Node mainContent = getMainContentSafely(content);
273
274 setContextAttribute(ctx, "content", content != null ? new ContentMap(wrapNodeForTemplate(content)) : null);
275 setContextAttribute(ctx, "def", definition);
276 setContextAttribute(ctx, "state", getAggregationStateSafely());
277 setContextAttribute(ctx, "model", model);
278 setContextAttribute(ctx, "actionResult", actionResult);
279
280 for (Entry<String, ContextAttributeConfiguration> entry : contextAttributes.entrySet()) {
281 setContextAttribute(ctx, entry.getKey(), Components.getComponent(entry.getValue().getComponentClass()));
282 }
283
284 }
285
286
287
288
289 protected Node getMainContentSafely(Node content) {
290 AggregationState state = getAggregationStateSafely();
291 return state == null ? content : state.getMainContentNode();
292 }
293
294
295
296
297 protected AggregationState getAggregationStateSafely() {
298 if (MgnlContext.isWebContext()) {
299 return MgnlContext.getAggregationState();
300 }
301 return null;
302 }
303
304
305
306
307
308
309
310 protected Node wrapNodeForModel(Node content) {
311 NodeUtil.deepUnwrap(content, HTMLEscapingNodeWrapper.class);
312 content = wrapWithChannelVisibilityWrapper(content);
313 content = wrapWithI18NWrapper(content);
314 return content;
315 }
316
317
318
319
320
321
322
323 protected Node wrapNodeForTemplate(Node content) {
324 content = wrapWithChannelVisibilityWrapper(content);
325 content = wrapWithI18NWrapper(content);
326 content = wrapWithHTMLEscapingWrapper(content);
327 return content;
328 }
329
330 private Node wrapWithHTMLEscapingWrapper(Node content) {
331 if (!NodeUtil.isWrappedWith(content, HTMLEscapingNodeWrapper.class)) {
332 content = new HTMLEscapingNodeWrapper(content, true);
333 }
334 return content;
335 }
336
337 private Node wrapWithI18NWrapper(Node content) {
338 if (!NodeUtil.isWrappedWith(content, I18nNodeWrapper.class)) {
339 content = new I18nNodeWrapper(content);
340 }
341 return content;
342 }
343
344 private Node wrapWithChannelVisibilityWrapper(Node content) {
345
346 if (ContentDecoratorUtil.isDecoratedWith(content, ChannelVisibilityContentDecorator.class)) {
347 return content;
348 }
349 AggregationState aggregationState = getAggregationStateSafely();
350 if (aggregationState == null) {
351 return content;
352 }
353 String channel = aggregationState.getChannel().getName();
354 if (StringUtils.isEmpty(channel) || channel.equalsIgnoreCase("all")) {
355 return content;
356 }
357 return new ChannelVisibilityContentDecorator(channel).wrapNode(content);
358 }
359
360 protected Object setContextAttribute(final Map<String, Object> ctx, final String name, Object value) {
361 return ctx.put(name, value);
362 }
363
364 public Map<String, ContextAttributeConfiguration> getContextAttributes() {
365 return contextAttributes;
366 }
367
368 public void setContextAttributes(Map<String, ContextAttributeConfiguration> contextAttributes) {
369 if (this.contextAttributes != null) {
370 this.contextAttributes.putAll(contextAttributes);
371 } else {
372 this.contextAttributes = contextAttributes;
373 }
374 }
375
376 public void addContextAttribute(String name, ContextAttributeConfiguration contextAttributeConfiguration) {
377 this.contextAttributes.put(name, contextAttributeConfiguration);
378 }
379
380
381
382
383 protected abstract Map<String, Object> newContext();
384
385
386
387
388 protected abstract void onRender(Node content, RenderableDefinition definition, RenderingContext renderingCtx, Map<String, Object> ctx, String templateScript) throws RenderException;
389
390 }