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