View Javadoc
1   /**
2    * This file Copyright (c) 2011-2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.rendering.context;
35  
36  import info.magnolia.cms.core.AggregationState;
37  import info.magnolia.objectfactory.annotation.LocalScoped;
38  import info.magnolia.rendering.engine.OutputProvider;
39  import info.magnolia.rendering.engine.RenderException;
40  import info.magnolia.rendering.engine.RenderExceptionHandler;
41  import info.magnolia.rendering.listeners.AbstractRenderingListener;
42  import info.magnolia.rendering.listeners.AbstractRenderingListener.RenderingListenerReturnCode;
43  import info.magnolia.rendering.template.AreaDefinition;
44  import info.magnolia.rendering.template.RenderableDefinition;
45  import info.magnolia.rendering.util.AppendableWriter;
46  
47  import java.io.IOException;
48  import java.io.OutputStream;
49  import java.util.ArrayList;
50  import java.util.Collection;
51  import java.util.EmptyStackException;
52  import java.util.Enumeration;
53  import java.util.Iterator;
54  import java.util.LinkedList;
55  import java.util.List;
56  import java.util.ListIterator;
57  import java.util.Map;
58  import java.util.Stack;
59  
60  import javax.inject.Inject;
61  import javax.inject.Provider;
62  import javax.jcr.Node;
63  
64  import org.slf4j.Logger;
65  import org.slf4j.LoggerFactory;
66  
67  /**
68   * RenderingContext implementation that uses AggregationState.
69   */
70  @LocalScoped
71  public class AggregationStateBasedRenderingContext implements RenderingContext {
72  
73      // FIXME we should not use the AggregationState anymore
74  
75      private final Logger log = LoggerFactory.getLogger(getClass());
76  
77      private static class StackState {
78  
79          RenderableDefinition renderableDefinition;
80          OutputProvider outputProvider;
81          Node legacyContent;
82  
83          private StackState(RenderableDefinition renderableDefinition, OutputProvider outputProvider, Node legacyContent) {
84              this.renderableDefinition = renderableDefinition;
85              this.outputProvider = outputProvider;
86              this.legacyContent = legacyContent;
87          }
88      }
89  
90      private final AggregationState aggregationState;
91      private final Stack<StackState> stack = new Stack<StackState>();
92      private RenderableDefinition currentRenderableDefinition;
93      private OutputProvider currentOutputProvider;
94      private final RenderExceptionHandler exceptionHandler;
95      private List<AbstractRenderingListener> listeners = new LinkedList<AbstractRenderingListener>();
96  
97      /**
98       * We keep the current state in local variables and start using the stack only for the second push operation. This
99       * variable is 0 before the first push, 1 when we have local variables set, and greater than 1 when we have things
100      * on stack.
101      */
102     private int depth = 0;
103 
104     @Inject
105     public AggregationStateBasedRenderingContext(Provider<AggregationState> aggregationStateProvider, RenderExceptionHandler exceptionHandler) {
106         this(aggregationStateProvider.get(), exceptionHandler);
107     }
108 
109     public AggregationStateBasedRenderingContext(AggregationState aggregationState, RenderExceptionHandler exceptionHandler) {
110         this.aggregationState = aggregationState;
111         this.exceptionHandler = exceptionHandler;
112     }
113 
114     @Override
115     public Node getMainContent() {
116         // there is still a possibility of call to this method before push!
117         return aggregationState.getMainContentNode();
118     }
119 
120     @Override
121     public Node getCurrentContent() {
122         return aggregationState.getCurrentContentNode();
123     }
124 
125     @Override
126     public RenderableDefinition getRenderableDefinition() {
127         return currentRenderableDefinition;
128     }
129 
130     @Override
131     public AreaDefinition getParentAreaDefinition() {
132         if (currentRenderableDefinition instanceof AreaDefinition) {
133             return (AreaDefinition) currentRenderableDefinition;
134         }
135 
136         Enumeration<StackState> elements = stack.elements();
137         while (elements.hasMoreElements()) {
138             StackState state = elements.nextElement();
139             if (state.renderableDefinition instanceof AreaDefinition) {
140                 return (AreaDefinition) state.renderableDefinition;
141             }
142         }
143         return null;
144     }
145 
146     /**
147      * @param level >= 1
148      * @return ancestor definition or <code>null</code> if such ancestor doesn't exist.
149      */
150     @Override
151     public RenderableDefinition getAncestorDefinition(int level) {
152         if (level <= 0 || level > stack.size()) {
153             log.error("Level {} ancestor of renderable definition for node '{}' doesn't exist.", level, this.getCurrentContent());
154             return null;
155         }
156         int index = stack.size() - level;
157         return stack.get(index).renderableDefinition;
158     }
159 
160     @Override
161     public void push(Node content, RenderableDefinition renderableDefinition) {
162         push(content, renderableDefinition, null);
163     }
164 
165     @Override
166     public void push(Node content, RenderableDefinition renderableDefinition, OutputProvider outputProvider) {
167 
168         // Creating the Content object can fail with an exception, by doing it before anything else we don't risk ending
169         // up having inconsistent state due to a partially completed push.
170         Node legacyContent = content;
171 
172         if (aggregationState.getMainContentNode() == null) {
173             aggregationState.setMainContentNode(content);
174         }
175 
176         if (depth > 0) {
177             stack.push(new StackState(currentRenderableDefinition, currentOutputProvider, aggregationState.getCurrentContentNode()));
178         }
179         aggregationState.setCurrentContentNode(content);
180         currentRenderableDefinition = renderableDefinition;
181         currentOutputProvider = outputProvider != null ? outputProvider : currentOutputProvider;
182         depth++;
183     }
184 
185     @Override
186     public void pop() {
187         if (depth == 0) {
188             throw new EmptyStackException();
189         } else if (depth == 1) {
190             aggregationState.setCurrentContentNode(null);
191             currentRenderableDefinition = null;
192             currentOutputProvider = null;
193         } else {
194             StackState state = stack.pop();
195             aggregationState.setCurrentContentNode(state.legacyContent);
196             currentRenderableDefinition = state.renderableDefinition;
197             currentOutputProvider = state.outputProvider;
198         }
199         depth--;
200         // Note that we do not restore main content
201     }
202 
203     @Override
204     public OutputProvider getOutputProvider() {
205         return currentOutputProvider;
206     }
207 
208     @Override
209     public AppendableWriter getAppendable() throws IOException {
210         return new AppendableWriter(this.currentOutputProvider.getAppendable());
211     }
212 
213     @Override
214     public OutputStream getOutputStream() throws IOException {
215         return this.currentOutputProvider.getOutputStream();
216     }
217 
218     @Override
219     public void handleException(RenderException renderException) {
220         exceptionHandler.handleException(renderException, this);
221     }
222 
223     @Override
224     public void setListeners(List<AbstractRenderingListener> listeners) {
225         this.listeners = listeners;
226     }
227 
228     @Override
229     public void addListener(AbstractRenderingListener renderingListener) {
230         this.listeners.add(renderingListener);
231     }
232 
233     // Calls before() on all listeners.
234     @Override
235     public Collection<RenderingListenerReturnCode> before(Node content, RenderableDefinition definition, Map<String, Object> contextObjects, OutputProvider out) {
236         Iterator<AbstractRenderingListener> iterator = this.listeners.iterator();
237         Collection<RenderingListenerReturnCode> results = new ArrayList<RenderingListenerReturnCode>();
238 
239         while (iterator.hasNext()) {
240             AbstractRenderingListener listener = iterator.next();
241             try {
242                 RenderingListenerReturnCode result = listener.before(content, definition, contextObjects, out);
243                 if (result != null) {
244                     results.add(result);
245                 }
246             } catch (Exception e) {
247                 log.error("{}.before() failed with exception ", listener, e);
248             }
249         }
250         return results;
251     }
252 
253     // Calls after() on all listeners.
254     @Override
255     public void after(Node content, RenderableDefinition definition, Map<String, Object> contextObjects, OutputProvider out) {
256         ListIterator<AbstractRenderingListener> iterator = this.listeners.listIterator(listeners.size());
257         Collection<RenderingListenerReturnCode> results = new ArrayList<RenderingListenerReturnCode>();
258 
259         while (iterator.hasPrevious()) {
260             AbstractRenderingListener listener = iterator.previous();
261             try {
262                 RenderingListenerReturnCode result = listener.after(content, definition, contextObjects, out);
263                 if (result != null) {
264                     results.add(result);
265                 }
266             } catch (Exception e) {
267                 log.error("{}.after() failed with exception ", listener, e);
268             }
269         }
270     }
271 }