View Javadoc

1   /**
2    * Copyright (C) 2005-2009 Alfresco Software Limited.
3    *
4    * This file is part of the Spring Surf Extension project.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *  http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.springframework.extensions.surf.mvc;
20  
21  import java.io.UnsupportedEncodingException;
22  import java.util.Enumeration;
23  import java.util.HashMap;
24  import java.util.Map;
25  
26  import javax.servlet.http.Cookie;
27  import javax.servlet.http.HttpServletRequest;
28  import javax.servlet.http.HttpServletResponse;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.springframework.extensions.surf.FrameworkUtil;
33  import org.springframework.extensions.surf.PageMapper;
34  import org.springframework.extensions.surf.PageMapperFactory;
35  import org.springframework.extensions.surf.RequestContext;
36  import org.springframework.extensions.surf.WebFrameworkConstants;
37  import org.springframework.extensions.surf.WebFrameworkServiceRegistry;
38  import org.springframework.extensions.surf.exception.PageMapperException;
39  import org.springframework.extensions.surf.exception.PlatformRuntimeException;
40  import org.springframework.extensions.surf.exception.RendererExecutionException;
41  import org.springframework.extensions.surf.exception.RequestDispatchException;
42  import org.springframework.extensions.surf.render.PresentationUtil;
43  import org.springframework.extensions.surf.render.RenderContext;
44  import org.springframework.extensions.surf.render.RenderFocus;
45  import org.springframework.extensions.surf.site.AuthenticationUtil;
46  import org.springframework.extensions.surf.site.Timer;
47  import org.springframework.extensions.surf.types.Page;
48  import org.springframework.extensions.surf.types.PageType;
49  import org.springframework.extensions.surf.types.TemplateInstance;
50  import org.springframework.extensions.surf.types.Theme;
51  import org.springframework.extensions.surf.util.Base64;
52  import org.springframework.extensions.webscripts.ProcessorModelHelper;
53  import org.springframework.extensions.webscripts.TemplateProcessor;
54  import org.springframework.extensions.webscripts.TemplateProcessorRegistry;
55  import org.springframework.extensions.webscripts.URLHelper;
56  import org.springframework.extensions.webscripts.connector.User;
57  import org.springframework.web.util.WebUtils;
58  
59  /**
60   * Default view implementation for Surf pages
61   * 
62   * @author muzquiano
63   */
64  public class PageView extends AbstractWebFrameworkView 
65  {
66      private static Log logger = LogFactory.getLog(PageView.class);
67      
68      private static final String ALF_REDIRECT_URL   = "alfRedirectUrl";
69      private static final String ALF_LAST_USERNAME = "alfLastUsername";
70      
71      public PageView(WebFrameworkServiceRegistry serviceRegistry)
72      {
73          super(serviceRegistry);
74      }
75  
76      /* (non-Javadoc)
77       * @see org.springframework.web.servlet.view.AbstractView#renderMergedOutputModel(java.util.Map, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
78       */
79      protected void renderMergedOutputModel(
80              Map model, HttpServletRequest request, HttpServletResponse response) throws Exception 
81      {
82          // bind a timer for reporting of dispatches
83          if (Timer.isTimerEnabled())
84          {
85              Timer.bindTimer(request);
86              Timer.start(request, "pageView");
87          }
88          
89          // the request context
90          RequestContext context = (RequestContext) FrameworkUtil.getCurrentRequestContext();
91  
92          // the page id (from view url)
93          String pageId = this.getUrl();
94          
95          // set up the request context for the given page
96          setupRequestContext(context, model, pageId);
97          
98          // in case our resolved page has changed (root page resolution)
99          pageId = context.getPageId();
100                 
101         // Expose the model object as request attributes.
102         exposeModelAsRequestAttributes(model, request);
103         
104         // Bind url helper into context (if not already bound)
105         if (context.getValue(ProcessorModelHelper.MODEL_URL) == null)
106         {
107             // build a URLHelper object one time - it is immutable and can be reused
108             URLHelper urlHelper = new URLHelper(context.getRequest());
109             context.setValue(ProcessorModelHelper.MODEL_URL, urlHelper);
110         }        
111         
112         // create the top render context
113         RenderContext renderContext = getRenderService().provideRenderContext(context, request, response);        
114         
115         // dispatch to render the page
116         try
117         {
118             if (Timer.isTimerEnabled())
119                 Timer.start(request, "pageView:" + pageId);
120             
121             dispatchContext(renderContext);
122             
123             org.springframework.web.servlet.DispatcherServlet a;
124             if (Timer.isTimerEnabled())
125                 Timer.stop(request, "pageView:" + pageId);            
126         }
127         catch (Throwable t)
128         {
129             // log but not mandatory handling
130             FrameworkUtil.logFullStacktrace(t);
131         }
132         finally
133         {
134             // stop the service timer and print out any timing information (if enabled)
135             if (Timer.isTimerEnabled())
136             {
137                 Timer.stop(request, "pageView");
138                 Timer.reportAll(request);
139                 Timer.unbindTimer(request);
140             }     
141             
142             // release any resources associated with the request context
143             context.release();            
144         }    
145 
146     }
147 
148     /**
149      * Expose forward request attributes.
150      * 
151      * @param request the request
152      */
153     protected void exposeForwardRequestAttributes(HttpServletRequest request) {
154         WebUtils.exposeForwardRequestAttributes(request);
155     }
156     
157     /* (non-Javadoc)
158      * @see org.alfresco.web.framework.dispatcher.AbstractDispatcher#dispatchContext(org.alfresco.web.framework.render.RenderContext)
159      */
160     public void dispatchContext(RenderContext context)
161         throws RequestDispatchException
162     {
163         String formatId = context.getFormatId();
164         String objectId = context.getCurrentObjectId();
165         String pageId = context.getPageId();
166         Page page = context.getPage();
167         
168         HttpServletRequest request = context.getRequest();
169         HttpServletResponse response = context.getResponse();
170         
171         if (page != null)
172         {
173             // redirect to login based on page authentication required 
174             boolean login = false;
175             User user = context.getUser();
176             switch (page.getAuthentication())
177             {
178                 case guest:
179                 {
180                     login = (user == null);
181                     break;
182                 }
183                 
184                 case user:
185                 {
186                     login = (user == null || AuthenticationUtil.isGuest(user.getId()));
187                     break;
188                 }
189                 
190                 case admin:
191                 {
192                     login = (user == null || !user.isAdmin());
193                     if (login)
194                     {
195                         // special case for admin - need to clear user context before
196                         // we can login again to "upgrade" our user authentication level
197                         AuthenticationUtil.clearUserContext(request);
198                     }
199                     break;
200                 }
201             }
202             
203             if (login)
204             {
205                 String loginPageId = null;
206                 
207                 // Consider the theme first - which can override common page types
208                 String themeId = (String) context.getThemeId();
209                 Theme theme = context.getModel().getTheme(themeId);
210                 if (theme != null)
211                 {
212                     loginPageId = theme.getPageId(PageType.PAGETYPE_LOGIN);
213                 }
214                 
215                 // Consider whether a system default has been set up
216                 if (loginPageId == null)
217                 {
218                     loginPageId = this.getWebFrameworkConfiguration().getDefaultPageTypeInstanceId(PageType.PAGETYPE_LOGIN);
219                 }
220                 
221                 Page loginPage = null;
222                 if (loginPageId != null)
223                 {
224                     loginPage = context.getModel().getPage(loginPageId);
225                     if (loginPage != null)
226                     {
227                         // get URL arguments as a map ready for rebuilding the request params
228                         Map<String, String> args = new HashMap<String, String>(
229                                 request.getParameterMap().size(), 1.0f);
230                         Enumeration names = request.getParameterNames();
231                         while (names.hasMoreElements())
232                         {
233                             String name = (String)names.nextElement();
234                             args.put(name, request.getParameter(name));
235                         }
236                         // construct redirection url
237                         String redirectUrl = request.getRequestURI() + 
238                             (request.getQueryString() != null ? ("?" + request.getQueryString()) : "");
239                         
240                         // set redirect url for use on login page template
241                         context.setValue(ALF_REDIRECT_URL, redirectUrl);
242                         
243                         // set last username if any
244                         Cookie cookie = AuthenticationUtil.getUsernameCookie(request);
245                         if (cookie != null)
246                         {
247                             try
248                             {
249                                 context.setValue(ALF_LAST_USERNAME, new String(Base64.decode(cookie.getValue()), "UTF-8"));
250                             }
251                             catch (UnsupportedEncodingException e)
252                             {
253                                 // should never happen
254                             }
255                         }
256                         
257                         // dispatch to the login page
258                         context.setPage(loginPage);
259                         dispatchPage(context, loginPage.getId(), formatId);
260                         
261                         // no need to process further as we have dispatched
262                         return;
263                     }
264                 }
265                 
266                 // if we get here then no login page was found - the webapp is not configured correctly
267                 if (loginPageId == null || loginPage == null)
268                 {
269                     throw new PlatformRuntimeException("No 'login' page type configured - but page auth required it.");
270                 }
271             }
272         }
273         
274         if (logger.isDebugEnabled())
275         {
276             debug(context, "Current Page ID: " + pageId);
277             debug(context, "Current Format ID: " + formatId);
278             debug(context, "Current Object ID: " + objectId);
279         }
280         
281         // if at this point there really is nothing to view...
282         if (page == null && objectId == null)
283         {
284             if (logger.isDebugEnabled())
285                 debug(context, "No Page or Object determined");
286             
287             // Go to the getting started page
288             try
289             {
290                 boolean handled = getRenderService().renderSystemPage(context, 
291                     WebFrameworkConstants.SYSTEM_PAGE_GETTING_STARTED);
292                 
293                 if (!handled)
294                 {
295                     throw new RequestDispatchException("Unable to discover a page to be dispatched - no target page or root page specified and a getting started page was not configured.");
296                 }
297             }
298             catch (RendererExecutionException ree)
299             {
300                 throw new RequestDispatchException(ree);
301             }
302         }
303         else
304         {
305             // we know we're dispatching to something...
306             // if we have a page specified, then we'll go there
307             if (pageId != null)
308             {
309                 if (logger.isDebugEnabled())
310                     debug(context, "Dispatching to Page: " + pageId);
311                 
312                 // if there happens to be a content item specified as well,
313                 // it will just become part of the context
314                 // i.e. if the content item doesn't determine the
315                 // destination page if the destination page is specified
316                 
317                 // we're dispatching to the current page
318                 dispatchPage(context, pageId, formatId);
319             }
320         }
321     }
322     
323     /* (non-Javadoc)
324      * @see org.alfresco.web.framework.dispatcher.PageDispatcher#dispatchPage(org.alfresco.web.framework.render.RenderContext, java.lang.String)
325      */
326     public void dispatchPage(RenderContext context, String pageId)
327         throws RequestDispatchException
328     {
329         dispatchPage(context, pageId, context.getFormatId());
330     }
331     
332     /* (non-Javadoc)
333      * @see org.alfresco.web.framework.dispatcher.PageDispatcher#dispatchPage(org.alfresco.web.framework.render.RenderContext, java.lang.String)
334      */
335     public void dispatchPage(RenderContext context, String pageId, String formatId)
336         throws RequestDispatchException
337     {
338         Page page = context.getPage();
339         if (page == null || !page.getId().equals(pageId))
340         {
341             // load the page and set onto context
342             page = context.getModel().getPage(pageId);
343             context.setPage(page);
344         }
345         
346         if (logger.isDebugEnabled())
347             debug(context, "Template ID: " + page.getTemplateId());
348         
349         TemplateInstance currentTemplate = page.getTemplate(context);
350         if (currentTemplate == null)
351         {
352             // if no template instance exists, we can provide a
353             // fast workaround where we can check to see if, by chance,
354             // a template exists with the same name as the page.
355             
356             TemplateProcessorRegistry templateProcessorRegistry = this.getServiceRegistry().getTemplatesContainer().getTemplateProcessorRegistry();
357             String validTemplatePath = templateProcessorRegistry.findValidTemplatePath(pageId);
358             if (validTemplatePath != null)
359             {
360                 TemplateProcessor templateProcessor = templateProcessorRegistry.getTemplateProcessor(validTemplatePath);
361                 if (templateProcessor != null)
362                 {
363                     // we have both discovered a template as well as a template processor that can handle this path
364                     // as such, let's cheat and auto-wire a dummy template instance up so that we can punch through
365                     // to the template without the need to code up all those objects
366                     
367                     // create a dummy template instance (does not persist)
368                     currentTemplate = context.getModel().newTemplate(pageId);
369                     currentTemplate.setTemplateTypeId(pageId);
370                     
371                     // bind to context
372                     context.setTemplate(currentTemplate);
373                 }
374             }
375         }
376         if (currentTemplate != null)
377         {
378             if (logger.isDebugEnabled())
379                 debug(context, "Rendering Page with template: " + currentTemplate.getId());
380             
381                PresentationUtil.renderPage(context, RenderFocus.BODY);
382         }
383         else
384         {
385             if (logger.isDebugEnabled())
386                 debug(context, "Unable to render Page - template was not found");
387             
388             try
389             {
390                 boolean handled = getRenderService().renderSystemPage(context, 
391                     WebFrameworkConstants.SYSTEM_PAGE_UNCONFIGURED);
392                 
393                 if (!handled)
394                 {
395                     throw new RequestDispatchException("The page '" + pageId + "' exists but a template association could not be determined.");
396                 }
397             }
398             catch (RendererExecutionException ree)
399             {
400                 throw new RequestDispatchException(ree);
401             }
402         }
403     }    
404     
405     /**
406      * Debug logger helper function.
407      * 
408      * @param context
409      * @param value
410      */
411     protected static void debug(RequestContext context, String value)
412     {
413         logger.debug("[" + context.getId() + "] " + value);
414     }    
415     
416     /**
417      * Sets up the request context with page binding information
418      * 
419      * @param context the context
420      * @param model the model
421      * @param pageId the page id
422      * 
423      * @throws PageMapperException the page mapper exception
424      */
425     public void setupRequestContext(RequestContext context, Map model, String pageId)
426         throws PageMapperException
427     {
428         // set the spring mvc model onto the context
429         context.setViewModel(model);
430 
431         // bind page
432         Page page = context.getModel().getPage(pageId);
433         if (page != null)
434         {
435             context.setPage(page);
436         }
437         
438         // run the page mapper to establish additional context
439         // this includes faulted objects, formats and more
440         PageMapperFactory factory = this.getServiceRegistry().getPageMapperFactory();
441         PageMapper pageMapper = factory.newInstance();
442         pageMapper.execute(context, (HttpServletRequest) context.getRequest());
443     }
444     
445 }