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 package org.objectledge.web.mvc.builders;
29
30 import org.objectledge.context.Context;
31 import org.objectledge.pipeline.ProcessingException;
32 import org.objectledge.pipeline.Valve;
33 import org.objectledge.templating.Template;
34 import org.objectledge.templating.TemplatingContext;
35 import org.objectledge.web.HttpContext;
36 import org.objectledge.web.mvc.MVCConstants;
37 import org.objectledge.web.mvc.MVCContext;
38 import org.objectledge.web.mvc.finders.MVCClassFinder;
39 import org.objectledge.web.mvc.finders.MVCTemplateFinder;
40 import org.objectledge.web.mvc.security.SecurityHelper;
41
42 /***
43 * Pipeline component for executing MVC view building.
44 *
45 * <p>
46 * The <code>BuilderExecutorValve</code> starts with a view name selected by the configured
47 * view request parameter ({@link org.objectledge.web.WebConfigurator#getViewToken()}).
48 * This parameter has to be defined and if not an
49 * {@link org.objectledge.web.mvc.builders.UndefinedViewParameterException} is thrown.
50 * At the same time the view name must point to either an existing
51 * {@link org.objectledge.web.mvc.builders.Builder} class or an existing template.
52 * If none of these exists a {@link org.objectledge.web.mvc.builders.MissingViewException}
53 * is thrown.
54 * If either the template or a builder has been found, the view building starts.
55 * </p>
56 *
57 * <p>
58 * For a given view name either a builder class or a template may be found using the defaulting
59 * strategy. This is useful for instance for applications having a set of different templates for
60 * presenting the same data. But <b>be warned</b> - defaulting may cause a lot of trouble and You should
61 * be aware of it.
62 * </p>
63 *
64 * <p>
65 * If the builder is found it is given the possiblity to route the processing to another builder.
66 * This is a rarely used functionality and has been introduced for similarity with older
67 * web application frameworks. Routing changes the currently selected view name and forces a new
68 * builder and template lookup.
69 * </p>
70 *
71 * <p>
72 * Having the either a builder or a template or both at the same time the build method of the
73 * builder is executed (missing builders and templates are replaced with
74 * {@link org.objectledge.web.mvc.builders.DefaultBuilder} and
75 * {@link org.objectledge.web.mvc.builders.DefaultTemplate} objects).
76 * Building creates a <code>String</code> represenation of view contents. The only exception is
77 * in case of a Builder which creates a direct response and writes it's content directly to the
78 * response (via {@link org.objectledge.web.HttpContext}), in such case view processing is stopped.
79 * </p>
80 *
81 * <p>Having the build results for a view, an enclosing view is looked up. The choice of the
82 * enclosing view is made upon:
83 * </p>
84 * <ul>
85 * <li>the {@link org.objectledge.web.mvc.builders.EnclosingView} object given by the current
86 * builder,</li>
87 * <li>or the choice made by the template designer using the
88 * {@link org.objectledge.web.mvc.builders.ViewEnclosureTool},</li>
89 * <li>or by means of defaulting strategy executed using
90 * {@link org.objectledge.web.mvc.finders.ViewFallbackSequence}.</li>
91 * </ul>
92 * <p>
93 * A detailed diagram of the enclosing view choice algorithm follows:
94 * </p>
95 *
96 * <img src="doc-files/BuilderExecutorValve-1.gif" alt="Enclosing view choice algorithm" />
97 *
98 * @author <a href="mailto:dgajda@caltha.pl">Damian Gajda</a>
99 * @version $Id: BuilderExecutorValve.java,v 1.36 2006/04/04 12:34:14 pablo Exp $
100 */
101 public class BuilderExecutorValve
102 implements Valve
103 {
104 /*** Finder for builder objects. */
105 protected MVCClassFinder classFinder;
106 /*** Finder for template objects. */
107 protected MVCTemplateFinder templateFinder;
108 /*** SecurityHelper for access checking. */
109 protected SecurityHelper securityHelper;
110 /*** ViewEnclosureManager for access template based view enclosures. */
111 protected ViewEnclosureManager viewEnclosureManager;
112 /*** maximum number of route calls per builder. */
113 protected int maxRouteCalls;
114 /*** maximum number of builder enclosures. */
115 protected int maxEnclosures;
116 /*** the default builder. */
117 protected Builder defaultBuilder;
118 /*** the default template. */
119 protected Template defaultTemplate;
120
121 /***
122 * Component constructor.
123 * @param context used application context
124 * @param classFinder finder for builder objects
125 * @param templateFinder finder for template objects
126 * @param securityHelper security helper for access checking
127 * @param viewEnclosureManager the template based enclosure manager
128 * @param maxRouteCalls maxmimal number of {@link Builder#route(String)} calls per {@link
129 * Builder}
130 * @param maxEnclosures maxmimal number of {@link Builder} enclosures
131 * (also {@link Builder#getEnclosingView(String)} calls)
132 */
133 public BuilderExecutorValve(Context context, MVCClassFinder classFinder,
134 MVCTemplateFinder templateFinder, SecurityHelper securityHelper,
135 ViewEnclosureManager viewEnclosureManager, int maxRouteCalls, int maxEnclosures)
136 {
137 this.classFinder = classFinder;
138 this.templateFinder = templateFinder;
139 this.securityHelper = securityHelper;
140 this.viewEnclosureManager = viewEnclosureManager;
141 this.maxRouteCalls = maxRouteCalls;
142 this.maxEnclosures = maxEnclosures;
143
144 this.defaultBuilder = new DefaultBuilder(context);
145 this.defaultTemplate = new DefaultTemplate();
146 }
147
148 /***
149 * Run view building starting from a view builder chosen in request parameters.
150 *
151 * @param context the thread's processing context.
152 * @throws ProcessingException if the processing fails.
153 */
154 public void process(Context context)
155 throws ProcessingException
156 {
157
158 HttpContext httpContext = HttpContext.getHttpContext(context);
159 MVCContext mvcContext = MVCContext.getMVCContext(context);
160 TemplatingContext templatingContext = TemplatingContext.getTemplatingContext(context);
161
162
163 String originalViewName = mvcContext.getView();
164 if(originalViewName == null || originalViewName.length() == 0)
165 {
166 throw new UndefinedViewParameterException();
167 }
168
169 String viewName = originalViewName;
170 String embeddedResult = null;
171 Builder builder = null;
172 Template template = null;
173
174 String innermostView = null;
175
176
177 int enclosures;
178 for (enclosures = 0; enclosures < maxEnclosures; enclosures++)
179 {
180 MVCClassFinder.Result builderResult = classFinder.findBuilder(viewName);
181 builder = builderResult.getBuilder();
182 if(builder != null)
183 {
184
185 int routeCalls;
186 for (routeCalls = 0; routeCalls < maxRouteCalls && builder != null; routeCalls++)
187 {
188
189 String routeBuilderName = builder.route(viewName);
190 if(routeBuilderName == null)
191 {
192 break;
193 }
194 mvcContext.setView(routeBuilderName);
195 viewName = routeBuilderName;
196 builderResult = classFinder.findBuilder(viewName);
197 builder = builderResult.getBuilder();
198 }
199 if(routeCalls >= maxRouteCalls)
200 {
201 throw new ProcessingException("Maximum number of builder reroutings exceeded");
202 }
203
204 securityHelper.checkSecurity(builder, context);
205 }
206
207 MVCTemplateFinder.Result templateResult = templateFinder.findBuilderTemplate(viewName);
208 template = templateResult.getTemplate();
209
210 if(enclosures == 0 &&
211 builderResult.fallbackPerformed() && templateResult.fallbackPerformed())
212 {
213 throw new MissingViewException(
214 "originalViewName="+originalViewName+", viewName="+viewName);
215 }
216
217
218 embeddedResult = embeddedResult == null ? "": embeddedResult;
219 innermostView = innermostView == null ? viewName : innermostView;
220 templatingContext.put(MVCConstants.EMBEDDED_PLACEHOLDER_KEY, embeddedResult);
221 templatingContext.put(MVCConstants.INNERMOST_VIEW_KEY, innermostView);
222
223
224 Builder actualBuilder = builder != null ? builder : defaultBuilder;
225 Template actualTemplate = template != null ? template : defaultTemplate;
226
227
228 try
229 {
230 embeddedResult = actualBuilder.build(actualTemplate, embeddedResult);
231 }
232 catch (BuildException e)
233 {
234 throw new ProcessingException(e);
235 }
236
237
238 if(httpContext.getDirectResponse())
239 {
240 return;
241 }
242
243
244
245
246 EnclosingView enclosingView = EnclosingView.DEFAULT;
247 if(builder != null)
248 {
249 enclosingView = builder.getEnclosingView(viewName);
250 }
251
252
253 if(enclosingView.defaultBehaviour())
254 {
255 enclosingView = viewEnclosureManager.getEnclosingView(enclosingView);
256 }
257
258 if(enclosingView.override())
259 {
260 viewName = enclosingView.getView();
261 }
262 else if(enclosingView.top())
263 {
264 break;
265 }
266 else if(enclosingView.defaultBehaviour())
267 {
268 if(viewName != null)
269 {
270 viewName = classFinder.findEnclosingViewName(viewName);
271 }
272 else
273 {
274 throw new ProcessingException("Top enclosing view not found");
275 }
276 }
277 else
278 {
279 throw new ProcessingException("Invalid enclosure specification for view "+viewName);
280 }
281 }
282
283
284 if(enclosures >= maxEnclosures)
285 {
286 throw new ProcessingException("Maximum number of builder enclosures exceeded");
287 }
288
289
290 mvcContext.setBuildResult(embeddedResult);
291 }
292 }