View Javadoc

1   /*
2    * Copyright (c) 2002-2014, Mairie de Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.portal.util.mvc.xpage;
35  
36  import fr.paris.lutece.portal.service.i18n.I18nService;
37  import fr.paris.lutece.portal.service.message.SiteMessageException;
38  import fr.paris.lutece.portal.service.plugin.Plugin;
39  import fr.paris.lutece.portal.service.security.UserNotSignedException;
40  import fr.paris.lutece.portal.service.template.AppTemplateService;
41  import fr.paris.lutece.portal.service.util.AppException;
42  import fr.paris.lutece.portal.service.util.AppLogService;
43  import fr.paris.lutece.portal.service.util.AppPropertiesService;
44  import fr.paris.lutece.portal.util.mvc.utils.MVCMessage;
45  import fr.paris.lutece.portal.util.mvc.utils.MVCMessageBox;
46  import fr.paris.lutece.portal.util.mvc.utils.MVCUtils;
47  import fr.paris.lutece.portal.util.mvc.xpage.annotations.Controller;
48  import fr.paris.lutece.portal.web.LocalVariables;
49  import fr.paris.lutece.portal.web.xpages.XPage;
50  import fr.paris.lutece.portal.web.xpages.XPageApplication;
51  import fr.paris.lutece.util.ErrorMessage;
52  import fr.paris.lutece.util.bean.BeanUtil;
53  import fr.paris.lutece.util.beanvalidation.BeanValidationUtil;
54  import fr.paris.lutece.util.html.HtmlTemplate;
55  import fr.paris.lutece.util.url.UrlItem;
56  
57  import java.io.IOException;
58  import java.io.OutputStream;
59  import java.io.PrintWriter;
60  import java.lang.reflect.InvocationTargetException;
61  import java.lang.reflect.Method;
62  import java.util.ArrayList;
63  import java.util.HashMap;
64  import java.util.List;
65  import java.util.Locale;
66  import java.util.Map;
67  import java.util.Map.Entry;
68  import java.util.Set;
69  
70  import javax.servlet.http.HttpServletRequest;
71  import javax.servlet.http.HttpServletResponse;
72  import javax.validation.ConstraintViolation;
73  
74  import org.apache.log4j.Logger;
75  import org.springframework.util.ReflectionUtils;
76  
77  
78  /**
79   * MVC XPage Application
80   */
81  public abstract class MVCApplication implements XPageApplication
82  {
83      private static final String MARK_ERRORS = "errors";
84      private static final String MARK_INFOS = "infos";
85      private static final String MARK_MESSAGE_BOX = "messageBox";
86      private static final String URL_PORTAL = "Portal.jsp";
87      private static final String PATH_PORTAL = "jsp/site/";
88      private static final String VIEW_MESSAGEBOX = "messageBox";
89      private static Logger _logger = MVCUtils.getLogger(  );
90      private List<ErrorMessage> _listErrors = new ArrayList<ErrorMessage>(  );
91      private List<ErrorMessage> _listInfos = new ArrayList<ErrorMessage>(  );
92      private MVCMessageBox _messageBox;
93      private Controller _controller = getClass(  ).getAnnotation( Controller.class );
94  
95      /**
96       * Returns the content of the page
97       *
98       * @param request The http request
99       * @param nMode The current mode
100      * @param plugin The plugin object
101      * @return The XPage
102      * @throws fr.paris.lutece.portal.service.message.SiteMessageException
103      *             Message displayed if an exception occurs
104      * @throws UserNotSignedException if an authentication is required by a view
105      */
106     @Override
107     public XPage getPage( HttpServletRequest request, int nMode, Plugin plugin )
108         throws SiteMessageException, UserNotSignedException
109     {
110         return processController( request );
111     }
112 
113     ////////////////////////////////////////////////////////////////////////////
114     // Controller
115 
116     /**
117      * XPage controller
118      * @param request The HTTP request
119      * @return The XPage
120      * @throws UserNotSignedException if an authentication is required by a view
121      * @throws SiteMessageException if a message is thrown by an action
122      */
123     private XPage processController( HttpServletRequest request )
124         throws UserNotSignedException, SiteMessageException
125     {
126         Method[] methods = ReflectionUtils.getAllDeclaredMethods( getClass(  ) );
127 
128         try
129         {
130             if ( isMessageBox( request ) )
131             {
132                 return messageBox( request );
133             }
134 
135             // Process views
136             Method m = MVCUtils.findViewAnnotedMethod( request, methods );
137 
138             if ( m != null )
139             {
140                 return (XPage) m.invoke( this, request );
141             }
142 
143             // Process actions
144             m = MVCUtils.findActionAnnotedMethod( request, methods );
145 
146             if ( m != null )
147             {
148                 return (XPage) m.invoke( this, request );
149             }
150 
151             // No view or action found so display the default view
152             m = MVCUtils.findDefaultViewMethod( methods );
153 
154             return (XPage) m.invoke( this, request );
155         }
156         catch ( InvocationTargetException e )
157         {
158             if ( e.getTargetException(  ) instanceof UserNotSignedException )
159             {
160                 throw (UserNotSignedException) e.getTargetException(  );
161             }
162 
163             if ( e.getTargetException(  ) instanceof SiteMessageException )
164             {
165                 throw (SiteMessageException) e.getTargetException(  );
166             }
167 
168             throw new AppException( "MVC Error dispaching view and action ", e );
169         }
170         catch ( IllegalAccessException e )
171         {
172             throw new AppException( "MVC Error dispaching view and action ", e );
173         }
174     }
175 
176     /**
177      * Returns the XPage name
178      * @return The XPage name
179      */
180     protected String getXPageName(  )
181     {
182         return _controller.xpageName(  );
183     }
184 
185     /**
186      * Returns the default page title
187      * @param locale The locale
188      * @return The default page title
189      */
190     protected String getDefaultPageTitle( Locale locale )
191     {
192         if ( !_controller.pageTitleProperty(  ).equals( "" ) )
193         {
194             return AppPropertiesService.getProperty( _controller.pageTitleProperty(  ) );
195         }
196         else if ( !_controller.pageTitleI18nKey(  ).equals( "" ) )
197         {
198             return I18nService.getLocalizedString( _controller.pageTitleI18nKey(  ), locale );
199         }
200 
201         return _controller.xpageName(  );
202     }
203 
204     /**
205      * Returns the default page path
206      * @param locale The locale
207      * @return The default pagepath
208      */
209     protected String getDefaultPagePath( Locale locale )
210     {
211         if ( !_controller.pagePathProperty(  ).equals( "" ) )
212         {
213             return AppPropertiesService.getProperty( _controller.pagePathProperty(  ) );
214         }
215         else if ( !_controller.pagePathI18nKey(  ).equals( "" ) )
216         {
217             return I18nService.getLocalizedString( _controller.pagePathI18nKey(  ), locale );
218         }
219 
220         return _controller.xpageName(  );
221     }
222 
223     ////////////////////////////////////////////////////////////////////////////
224     // XPage utils
225 
226     /**
227      * Returns a new XPage object with default values
228      * @return An XPage Object
229      */
230     protected XPage getXPage(  )
231     {
232         XPage page = new XPage(  );
233 
234         page.setTitle( getDefaultPageTitle( Locale.getDefault(  ) ) );
235         page.setPathLabel( getDefaultPagePath( Locale.getDefault(  ) ) );
236 
237         return page;
238     }
239 
240     /**
241      * Returns a new XPage object with default values and the content filled
242      * by a template
243      * @param strTemplate The template
244      * @return An XPage Object
245      */
246     protected XPage getXPage( String strTemplate )
247     {
248         XPage page = getXPage(  );
249 
250         HtmlTemplate t = AppTemplateService.getTemplate( strTemplate );
251         page.setContent( t.getHtml(  ) );
252 
253         return page;
254     }
255 
256     /**
257      * Returns a new XPage object with default values and the content filled
258      * by a template using a default model and for a given locale
259      * @param strTemplate The template
260      * @param locale The locale
261      * @return An XPage Object
262      */
263     protected XPage getXPage( String strTemplate, Locale locale )
264     {
265         return getXPage( strTemplate, locale, getModel(  ) );
266     }
267 
268     /**
269      * Returns a new XPage object with default values and the content filled
270      * by a template using a given model and for a given locale
271      * @param strTemplate The template
272      * @param locale The locale
273      * @param model The model
274      *
275      * @return An XPage Object
276      */
277     protected XPage getXPage( String strTemplate, Locale locale, Map<String, Object> model )
278     {
279         XPage page = getXPage(  );
280 
281         HtmlTemplate t = AppTemplateService.getTemplate( strTemplate, locale, model );
282         page.setContent( t.getHtml(  ) );
283         page.setTitle( getDefaultPageTitle( locale ) );
284         page.setPathLabel( getDefaultPagePath( locale ) );
285 
286         return page;
287     }
288 
289     /**
290      * Get a model Object filled with default values
291      * @return The model
292      */
293     protected Map<String, Object> getModel(  )
294     {
295         Map<String, Object> model = new HashMap<String, Object>(  );
296         fillCommons( model );
297 
298         return model;
299     }
300 
301     ////////////////////////////////////////////////////////////////////////////
302     // Bean processing
303 
304     /**
305      * Populate a bean using parameters in http request
306      * @param bean bean to populate
307      * @param request http request
308      */
309     protected void populate( Object bean, HttpServletRequest request )
310     {
311         BeanUtil.populate( bean, request );
312     }
313 
314     /**
315      * Validate a bean. If the validation failed, error messages of this
316      * MVCApplication are updated.< br/>
317      * This method should be used only if error messages of
318      * constraints of the bean are NOT i18n Keys. If they are I18n keys, the
319      * method {@link #validateBean(Object, Locale)} should be used instead.
320      * @param <T> The bean class
321      * @param bean The bean
322      * @return true if validated otherwise false
323      */
324     protected <T> boolean validateBean( T bean )
325     {
326         Set<ConstraintViolation<T>> errors = BeanValidationUtil.validate( bean );
327 
328         if ( errors.isEmpty(  ) )
329         {
330             return true;
331         }
332 
333         for ( ConstraintViolation<T> constraint : errors )
334         {
335             MVCMessage error = new MVCMessage(  );
336             error.setMessage( constraint.getMessage(  ) );
337             _listErrors.add( error );
338         }
339 
340         return false;
341     }
342 
343     /**
344      * Validate a bean. If the validation failed, error messages of this
345      * MVCApplication are updated.< br/>
346      * This method should be used only if error messages of constraints of the
347      * bean are i18n Keys. If they are not I18n keys, the method
348      * {@link #validateBean(Object)} should be used instead.
349      * @param <T> The bean class
350      * @param bean The bean
351      * @param locale The locale
352      * @return true if validated otherwise false
353      */
354     protected <T> boolean validateBean( T bean, Locale locale )
355     {
356         Set<ConstraintViolation<T>> errors = BeanValidationUtil.validate( bean );
357 
358         if ( errors.isEmpty(  ) )
359         {
360             return true;
361         }
362 
363         for ( ConstraintViolation<T> constraint : errors )
364         {
365             MVCMessage error = new MVCMessage(  );
366             error.setMessage( I18nService.getLocalizedString( constraint.getMessage(  ), locale ) );
367             _listErrors.add( error );
368         }
369 
370         return false;
371     }
372 
373     /**
374      * Add an error message. The error message must NOT be an I18n key.
375      * @param strMessage The message
376      */
377     protected void addError( String strMessage )
378     {
379         _listErrors.add( new MVCMessage( strMessage ) );
380     }
381 
382     /**
383      * Add an error message. The error message must be an I18n key.
384      * @param strMessageKey The message
385      * @param locale The locale to display the message in
386      */
387     protected void addError( String strMessageKey, Locale locale )
388     {
389         _listErrors.add( new MVCMessage( I18nService.getLocalizedString( strMessageKey, locale ) ) );
390     }
391 
392     /**
393      * Add an info message. The info message must NOT be an I18n key.
394      * @param strMessage The message
395      */
396     protected void addInfo( String strMessage )
397     {
398         _listInfos.add( new MVCMessage( strMessage ) );
399     }
400 
401     /**
402      * Add an info message. The info message must be an I18n key.
403      * @param strMessageKey The message key
404      * @param locale The locale to display the message in
405      */
406     protected void addInfo( String strMessageKey, Locale locale )
407     {
408         _listInfos.add( new MVCMessage( I18nService.getLocalizedString( strMessageKey, locale ) ) );
409     }
410 
411     /**
412      * Fill the model with commons objects used in templates
413      * @param model The model
414      */
415     protected void fillCommons( Map<String, Object> model )
416     {
417         List<ErrorMessage> listErrors = new ArrayList<ErrorMessage>( _listErrors );
418         List<ErrorMessage> listInfos = new ArrayList<ErrorMessage>( _listInfos );
419         model.put( MARK_ERRORS, listErrors );
420         model.put( MARK_INFOS, listInfos );
421         _listErrors.clear(  );
422         _listInfos.clear(  );
423     }
424 
425     ////////////////////////////////////////////////////////////////////////////
426     // Redirect utils
427 
428     /**
429      * Redirect to requested page
430      *
431      * @param request the http request
432      * @param strTarget the targeted page
433      * @return the page requested
434      */
435     protected XPage redirect( HttpServletRequest request, String strTarget )
436     {
437         HttpServletResponse response = LocalVariables.getResponse(  );
438 
439         try
440         {
441             _logger.debug( "Redirect :" + strTarget );
442             response.sendRedirect( strTarget );
443         }
444         catch ( IOException e )
445         {
446             _logger.error( "Unable to redirect : " + strTarget + " : " + e.getMessage(  ), e );
447         }
448 
449         return new XPage(  );
450     }
451 
452     /**
453      * Redirect to an url defined by given parameters
454      * @param request The HTTP request
455      * @param strView The View name
456      * @param strParameter The additional parameter
457      * @param nValue The additional parameter's value
458      * @return The XPage redirected
459      */
460     protected XPage redirect( HttpServletRequest request, String strView, String strParameter, int nValue )
461     {
462         UrlItem url = new UrlItem( getViewUrl( strView ) );
463         url.addParameter( strParameter, nValue );
464 
465         return redirect( request, url.getUrl( ) );
466     }
467 
468     /**
469      * Redirect to an url defined by given parameters
470      * @param request The HTTP request
471      * @param strView The View name
472      * @param strParameter1 The first additional parameter
473      * @param nValue1 The first additional parameter's value
474      * @param strParameter2 The second additionnal parameter
475      * @param nValue2 The second additionnal parameter's value
476      * @return The XPage redirected
477      */
478     protected XPage redirect( HttpServletRequest request, String strView, String strParameter1, int nValue1,
479         String strParameter2, int nValue2 )
480     {
481         UrlItem url = new UrlItem( getViewUrl( strView ) );
482         url.addParameter( strParameter1, nValue1 );
483         url.addParameter( strParameter2, nValue2 );
484 
485         return redirect( request, url.getUrl(  ) );
486     }
487 
488     /**
489      * Redirect to an url defined by given parameters
490      * @param request The HTTP request
491      * @param strView The View name
492      * @param additionalParameters A map containing parameters to add to the
493      *            URL. Keys of the map are parameters name, and values are
494      *            parameters values
495      * @return The XPage redirected
496      */
497     protected XPage redirect( HttpServletRequest request, String strView, Map<String, String> additionalParameters )
498     {
499         UrlItem url = new UrlItem( getViewUrl( strView ) );
500 
501         if ( additionalParameters != null )
502         {
503             for ( Entry<String, String> entry : additionalParameters.entrySet( ) )
504             {
505                 url.addParameter( entry.getKey( ), entry.getValue( ) );
506             }
507         }
508         return redirect( request, url.getUrl( ) );
509     }
510 
511     /**
512      * Redirect to requested view
513      *
514      * @param request the http request
515      * @param strView the targeted view
516      * @return the page requested
517      */
518     protected XPage redirectView( HttpServletRequest request, String strView )
519     {
520         return redirect( request, getViewUrl( strView ) );
521     }
522 
523     /**
524      * Get a View URL
525      * @param strView The view name
526      * @return The URL
527      */
528     protected String getViewUrl( String strView )
529     {
530         UrlItem url = new UrlItem( URL_PORTAL );
531         url.addParameter( MVCUtils.PARAMETER_PAGE, getXPageName(  ) );
532         url.addParameter( MVCUtils.PARAMETER_VIEW, strView );
533 
534         return url.getUrl(  );
535     }
536 
537     /**
538      * Gets the view URL with the JSP path
539      * @param strView The view
540      * @return The URL
541      */
542     protected String getViewFullUrl( String strView )
543     {
544         return PATH_PORTAL + getViewUrl( strView );
545     }
546 
547     /**
548      * Get Action URL
549      * @param strAction The view name
550      * @return The URL
551      */
552     protected String getActionUrl( String strAction )
553     {
554         UrlItem url = new UrlItem( URL_PORTAL );
555         url.addParameter( MVCUtils.PARAMETER_PAGE, getXPageName(  ) );
556         url.addParameter( MVCUtils.PARAMETER_ACTION, strAction );
557 
558         return url.getUrl(  );
559     }
560 
561     /**
562      * Get Action URL
563      * @param strAction The view name
564      * @return The URL
565      */
566     protected String getActionFullUrl( String strAction )
567     {
568         return PATH_PORTAL + getActionUrl( strAction );
569     }
570 
571     /**
572      * Initiates a file download
573      * @param strData Data of the file to download
574      * @param strFilename Name of file
575      * @param strContentType content type to set to the response
576      * @return The page requested
577      */
578     protected XPage download( String strData, String strFilename, String strContentType )
579     {
580         HttpServletResponse response = LocalVariables.getResponse(  );
581         PrintWriter out = null;
582         response.setHeader( "Content-Disposition", "attachment; filename=\"" + strFilename + "\";" );
583         addDownloadHeaderToResponse( response, strFilename, strContentType );
584 
585         try
586         {
587             out = response.getWriter(  );
588             out.print( strData );
589         }
590         catch ( IOException e )
591         {
592             AppLogService.error( e.getStackTrace(  ), e );
593         }
594         finally
595         {
596             if ( out != null )
597             {
598                 out.close(  );
599             }
600         }
601 
602         return new XPage(  );
603     }
604 
605     /**
606      * Initiates a download of a byte array
607      * @param data Data to download
608      * @param strFilename Name of the downloaded file
609      * @param strContentType Content type to set to the response
610      * @return The page requested
611      */
612     protected XPage download( byte[] data, String strFilename, String strContentType )
613     {
614         HttpServletResponse response = LocalVariables.getResponse(  );
615         OutputStream os;
616         addDownloadHeaderToResponse( response, strFilename, strContentType );
617 
618         try
619         {
620             os = response.getOutputStream(  );
621             os.write( data );
622             os.close(  );
623         }
624         catch ( IOException e )
625         {
626             AppLogService.error( e.getStackTrace(  ), e );
627         }
628 
629         return new XPage(  );
630     }
631 
632     /**
633      * Add download headers to the response
634      * @param response The response
635      * @param strFilename The name of the file to download
636      * @param strContentType The content type of the downloaded file
637      */
638     private void addDownloadHeaderToResponse( HttpServletResponse response, String strFilename, String strContentType )
639     {
640         response.setHeader( "Content-Disposition", "attachment; filename=\"" + strFilename + "\";" );
641         response.setHeader( "Content-type", strContentType );
642         response.addHeader( "Content-Encoding", "UTF-8" );
643         response.addHeader( "Pragma", "public" );
644         response.addHeader( "Expires", "0" );
645         response.addHeader( "Cache-Control", "must-revalidate,post-check=0,pre-check=0" );
646     }
647 
648     ////////////////////////////////////////////////////////////////////////////
649     // MESSAGE BOX MANAGEMENT
650 
651     /**
652      * Redirect to a Message Box page
653      * @param request The HTTP request
654      * @param messageBox The MessageBox infos
655      * @return A redirect XPage
656      */
657     protected XPage redirectMessageBox( HttpServletRequest request, MVCMessageBox messageBox )
658     {
659         _messageBox = messageBox;
660 
661         return redirectView( request, VIEW_MESSAGEBOX );
662     }
663 
664     /**
665      * Check if a message box is asked for
666      * @param request The HTTP request
667      * @return If a message box is asked
668      */
669     private boolean isMessageBox( HttpServletRequest request )
670     {
671         String strView = request.getParameter( MVCUtils.PARAMETER_VIEW );
672 
673         return ( ( strView != null ) && ( strView.equals( VIEW_MESSAGEBOX ) ) );
674     }
675 
676     /**
677      * Default getLocale() implementation. Could be overriden
678      * @param request The HTTP request
679      * @return The Locale
680      */
681     protected Locale getLocale( HttpServletRequest request )
682     {
683         return request.getLocale(  );
684     }
685 
686     /**
687      * Display the Message BOX
688      * @param request The HTTP request
689      * @return The message box
690      */
691     private XPage messageBox( HttpServletRequest request )
692     {
693         _messageBox.localize( getLocale( request ) );
694 
695         Map<String, Object> model = getModel(  );
696         model.put( MARK_MESSAGE_BOX, _messageBox );
697 
698         return getXPage( _messageBox.getTemplate(  ), getLocale( request ), model );
699     }
700 }