View Javadoc

1   // 
2   // Copyright (c) 2003, Caltha - Gajda, Krzewski, Mach, Potempski Sp.J. 
3   // All rights reserved. 
4   // 
5   // Redistribution and use in source and binary forms, with or without modification,  
6   // are permitted provided that the following conditions are met: 
7   //  
8   // * Redistributions of source code must retain the above copyright notice,  
9   // this list of conditions and the following disclaimer. 
10  // * Redistributions in binary form must reproduce the above copyright notice,  
11  // this list of conditions and the following disclaimer in the documentation  
12  // and/or other materials provided with the distribution. 
13  // * Neither the name of the Caltha - Gajda, Krzewski, Mach, Potempski Sp.J.  
14  // nor the names of its contributors may be used to endorse or promote products  
15  // derived from this software without specific prior written permission. 
16  // 
17  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"  
18  // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED  
19  // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
20  // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,  
21  // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,  
22  // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
23  // OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,  
24  // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)  
25  // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE  
26  // POSSIBILITY OF SUCH DAMAGE. 
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         // take them in through pico?        
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 		// setup used contexts
158         HttpContext httpContext = HttpContext.getHttpContext(context);
159 		MVCContext mvcContext = MVCContext.getMVCContext(context);
160         TemplatingContext templatingContext = TemplatingContext.getTemplatingContext(context);
161 	
162 		// get initial builder, template and embedded result
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         // usually equal to request's 'view' parameter, but may be affected by builder routing
174         String innermostView = null;
175 		
176 		// start processing
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                 // route builder -------------------------------------------------------------------
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                 // security check ------------------------------------------------------------------
204                 securityHelper.checkSecurity(builder, context);
205             }
206             // get template ------------------------------------------------------------------------
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 			// embedded results --------------------------------------------------------------------
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             // default builder / template if not resolved up to this point -------------------------
224             Builder actualBuilder = builder != null ? builder : defaultBuilder;
225             Template actualTemplate = template != null ? template : defaultTemplate;
226             
227             // perform actual build ----------------------------------------------------------------
228 			try
229 	        {
230             	embeddedResult = actualBuilder.build(actualTemplate, embeddedResult);
231 	        }
232 	        catch (BuildException e)
233 	        {
234 	            throw new ProcessingException(e);
235 	        }
236 
237             // escape on direct response -----------------------------------------------------------
238             if(httpContext.getDirectResponse())
239             {
240                 return;
241             }
242             
243             // get next view build level -----------------------------------------------------------
244             
245             // ask the builder about requested enclosure -------------------------------------------
246             EnclosingView enclosingView = EnclosingView.DEFAULT;
247             if(builder != null)
248             {
249                 enclosingView = builder.getEnclosingView(viewName);
250             }
251             
252             // ask view enclosure manager that wraps viewEnclosureTool available in templates ------
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 		// did not reach the top builder -----------------------------------------------------------
284 		if(enclosures >= maxEnclosures)
285 		{
286 			throw new ProcessingException("Maximum number of builder enclosures exceeded");
287 		}
288 
289 		// store building result -------------------------------------------------------------------
290 		mvcContext.setBuildResult(embeddedResult);
291 	}
292 }