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