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