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.l10n.LocaleService;
50  import fr.paris.lutece.portal.web.xpages.XPage;
51  import fr.paris.lutece.portal.web.xpages.XPageApplication;
52  import fr.paris.lutece.util.ErrorMessage;
53  import fr.paris.lutece.util.bean.BeanUtil;
54  import fr.paris.lutece.util.beanvalidation.BeanValidationUtil;
55  import fr.paris.lutece.util.html.HtmlTemplate;
56  import fr.paris.lutece.util.url.UrlItem;
57  
58  import org.apache.log4j.Logger;
59  
60  import org.springframework.util.ReflectionUtils;
61  
62  import java.io.IOException;
63  import java.io.OutputStream;
64  import java.io.PrintWriter;
65  
66  import java.lang.reflect.InvocationTargetException;
67  import java.lang.reflect.Method;
68  
69  import java.util.ArrayList;
70  import java.util.HashMap;
71  import java.util.List;
72  import java.util.Locale;
73  import java.util.Map;
74  import java.util.Map.Entry;
75  import java.util.Set;
76  
77  import javax.servlet.ServletOutputStream;
78  import javax.servlet.http.HttpServletRequest;
79  import javax.servlet.http.HttpServletResponse;
80  
81  import javax.validation.ConstraintViolation;
82  
83  
84  /**
85   * MVC XPage Application
86   */
87  public abstract class MVCApplication implements XPageApplication
88  {
89      private static final long serialVersionUID = 6093635383465830355L;
90      private static final String MARK_ERRORS = "errors";
91      private static final String MARK_INFOS = "infos";
92      private static final String MARK_MESSAGE_BOX = "messageBox";
93      private static final String URL_PORTAL = "Portal.jsp";
94      private static final String PATH_PORTAL = "jsp/site/";
95      private static final String VIEW_MESSAGEBOX = "messageBox";
96      private static final String CONTENT_TYPE_JSON = "application/json";
97      private static final String CONTENT_TYPE_XML = "application/xml";
98      private static Logger _logger = MVCUtils.getLogger(  );
99      private List<ErrorMessage> _listErrors = new ArrayList<ErrorMessage>(  );
100     private List<ErrorMessage> _listInfos = new ArrayList<ErrorMessage>(  );
101     private MVCMessageBox _messageBox;
102     private Controller _controller = getClass(  ).getAnnotation( Controller.class );
103 
104     /**
105      * Returns the content of the page
106      *
107      * @param request The http request
108      * @param nMode The current mode
109      * @param plugin The plugin object
110      * @return The XPage
111      * @throws fr.paris.lutece.portal.service.message.SiteMessageException
112      *             Message displayed if an exception occurs
113      * @throws UserNotSignedException if an authentication is required by a view
114      */
115     @Override
116     public XPage getPage( HttpServletRequest request, int nMode, Plugin plugin )
117         throws SiteMessageException, UserNotSignedException
118     {
119         return processController( request );
120     }
121 
122     ////////////////////////////////////////////////////////////////////////////
123     // Controller
124 
125     /**
126      * XPage controller
127      * @param request The HTTP request
128      * @return The XPage
129      * @throws UserNotSignedException if an authentication is required by a view
130      * @throws SiteMessageException if a message is thrown by an action
131      */
132     private XPage processController( HttpServletRequest request )
133         throws UserNotSignedException, SiteMessageException
134     {
135         Method[] methods = ReflectionUtils.getAllDeclaredMethods( getClass(  ) );
136 
137         try
138         {
139             if ( isMessageBox( request ) )
140             {
141                 return messageBox( request );
142             }
143 
144             // Process views
145             Method m = MVCUtils.findViewAnnotedMethod( request, methods );
146 
147             if ( m != null )
148             {
149                 return (XPage) m.invoke( this, request );
150             }
151 
152             // Process actions
153             m = MVCUtils.findActionAnnotedMethod( request, methods );
154 
155             if ( m != null )
156             {
157                 return (XPage) m.invoke( this, request );
158             }
159 
160             // No view or action found so display the default view
161             m = MVCUtils.findDefaultViewMethod( methods );
162 
163             return (XPage) m.invoke( this, request );
164         }
165         catch ( InvocationTargetException e )
166         {
167             if ( e.getTargetException(  ) instanceof UserNotSignedException )
168             {
169                 throw (UserNotSignedException) e.getTargetException(  );
170             }
171 
172             if ( e.getTargetException(  ) instanceof SiteMessageException )
173             {
174                 throw (SiteMessageException) e.getTargetException(  );
175             }
176 
177             throw new AppException( "MVC Error dispaching view and action ", e );
178         }
179         catch ( IllegalAccessException e )
180         {
181             throw new AppException( "MVC Error dispaching view and action ", e );
182         }
183     }
184 
185     /**
186      * Returns the XPage name
187      * @return The XPage name
188      */
189     protected String getXPageName(  )
190     {
191         return _controller.xpageName(  );
192     }
193 
194     /**
195      * Returns the default page title
196      * @param locale The locale
197      * @return The default page title
198      */
199     protected String getDefaultPageTitle( Locale locale )
200     {
201         if ( !_controller.pageTitleProperty(  ).equals( "" ) )
202         {
203             return AppPropertiesService.getProperty( _controller.pageTitleProperty(  ) );
204         }
205         else if ( !_controller.pageTitleI18nKey(  ).equals( "" ) )
206         {
207             return I18nService.getLocalizedString( _controller.pageTitleI18nKey(  ), locale );
208         }
209 
210         return _controller.xpageName(  );
211     }
212 
213     /**
214      * Returns the default page path
215      * @param locale The locale
216      * @return The default pagepath
217      */
218     protected String getDefaultPagePath( Locale locale )
219     {
220         if ( !_controller.pagePathProperty(  ).equals( "" ) )
221         {
222             return AppPropertiesService.getProperty( _controller.pagePathProperty(  ) );
223         }
224         else if ( !_controller.pagePathI18nKey(  ).equals( "" ) )
225         {
226             return I18nService.getLocalizedString( _controller.pagePathI18nKey(  ), locale );
227         }
228 
229         return _controller.xpageName(  );
230     }
231 
232     ////////////////////////////////////////////////////////////////////////////
233     // XPage utils
234 
235     /**
236      * Returns a new XPage object with default values
237      * @return An XPage Object
238      */
239     protected XPage getXPage(  )
240     {
241         XPage page = new XPage(  );
242 
243         page.setTitle( getDefaultPageTitle( LocaleService.getDefault(  ) ) );
244         page.setPathLabel( getDefaultPagePath( LocaleService.getDefault(  ) ) );
245 
246         return page;
247     }
248 
249     /**
250      * Returns a new XPage object with default values and the content filled
251      * by a template
252      * @param strTemplate The template
253      * @return An XPage Object
254      */
255     protected XPage getXPage( String strTemplate )
256     {
257         XPage page = getXPage(  );
258 
259         HtmlTemplate t = AppTemplateService.getTemplate( strTemplate );
260         page.setContent( t.getHtml(  ) );
261 
262         return page;
263     }
264 
265     /**
266      * Returns a new XPage object with default values and the content filled
267      * by a template using a default model and for a given locale
268      * @param strTemplate The template
269      * @param locale The locale
270      * @return An XPage Object
271      */
272     protected XPage getXPage( String strTemplate, Locale locale )
273     {
274         return getXPage( strTemplate, locale, getModel(  ) );
275     }
276 
277     /**
278      * Returns a new XPage object with default values and the content filled
279      * by a template using a given model and for a given locale
280      * @param strTemplate The template
281      * @param locale The locale
282      * @param model The model
283      *
284      * @return An XPage Object
285      */
286     protected XPage getXPage( String strTemplate, Locale locale, Map<String, Object> model )
287     {
288         XPage page = getXPage(  );
289 
290         HtmlTemplate t = AppTemplateService.getTemplate( strTemplate, locale, model );
291         page.setContent( t.getHtml(  ) );
292         page.setTitle( getDefaultPageTitle( locale ) );
293         page.setPathLabel( getDefaultPagePath( locale ) );
294 
295         return page;
296     }
297 
298     /**
299      * Get a model Object filled with default values
300      * @return The model
301      */
302     protected Map<String, Object> getModel(  )
303     {
304         Map<String, Object> model = new HashMap<String, Object>(  );
305         fillCommons( model );
306 
307         return model;
308     }
309 
310     ////////////////////////////////////////////////////////////////////////////
311     // Bean processing
312 
313     /**
314      * Populate a bean using parameters in http request
315      * @param bean bean to populate
316      * @param request http request
317      */
318     protected void populate( Object bean, HttpServletRequest request )
319     {
320         BeanUtil.populate( bean, request );
321     }
322 
323     /**
324      * Validate a bean. If the validation failed, error messages of this
325      * MVCApplication are updated.< br/>
326      * This method should be used only if error messages of
327      * constraints of the bean are NOT i18n Keys. If they are I18n keys, the
328      * method {@link #validateBean(Object, Locale)} should be used instead.
329      * @param <T> The bean class
330      * @param bean The bean
331      * @return true if validated otherwise false
332      */
333     protected <T> boolean validateBean( T bean )
334     {
335         Set<ConstraintViolation<T>> errors = BeanValidationUtil.validate( bean );
336 
337         if ( errors.isEmpty(  ) )
338         {
339             return true;
340         }
341 
342         for ( ConstraintViolation<T> constraint : errors )
343         {
344             MVCMessage error = new MVCMessage(  );
345             error.setMessage( constraint.getMessage(  ) );
346             _listErrors.add( error );
347         }
348 
349         return false;
350     }
351 
352     /**
353      * Validate a bean. If the validation failed, error messages of this
354      * MVCApplication are updated.< br/>
355      * This method should be used only if error messages of constraints of the
356      * bean are i18n Keys. If they are not I18n keys, the method
357      * {@link #validateBean(Object)} should be used instead.
358      * @param <T> The bean class
359      * @param bean The bean
360      * @param locale The locale
361      * @return true if validated otherwise false
362      */
363     protected <T> boolean validateBean( T bean, Locale locale )
364     {
365         Set<ConstraintViolation<T>> errors = BeanValidationUtil.validate( bean );
366 
367         if ( errors.isEmpty(  ) )
368         {
369             return true;
370         }
371 
372         for ( ConstraintViolation<T> constraint : errors )
373         {
374             MVCMessage error = new MVCMessage(  );
375             error.setMessage( I18nService.getLocalizedString( constraint.getMessage(  ), locale ) );
376             _listErrors.add( error );
377         }
378 
379         return false;
380     }
381 
382     /**
383      * Add an error message. The error message must NOT be an I18n key.
384      * @param strMessage The message
385      */
386     protected void addError( String strMessage )
387     {
388         _listErrors.add( new MVCMessage( strMessage ) );
389     }
390 
391     /**
392      * Add an error message. The error message must be an I18n key.
393      * @param strMessageKey The message
394      * @param locale The locale to display the message in
395      */
396     protected void addError( String strMessageKey, Locale locale )
397     {
398         _listErrors.add( new MVCMessage( I18nService.getLocalizedString( strMessageKey, locale ) ) );
399     }
400 
401     /**
402      * Add an info message. The info message must NOT be an I18n key.
403      * @param strMessage The message
404      */
405     protected void addInfo( String strMessage )
406     {
407         _listInfos.add( new MVCMessage( strMessage ) );
408     }
409 
410     /**
411      * Add an info message. The info message must be an I18n key.
412      * @param strMessageKey The message key
413      * @param locale The locale to display the message in
414      */
415     protected void addInfo( String strMessageKey, Locale locale )
416     {
417         _listInfos.add( new MVCMessage( I18nService.getLocalizedString( strMessageKey, locale ) ) );
418     }
419 
420     /**
421      * Fill the model with commons objects used in templates
422      * @param model The model
423      */
424     protected void fillCommons( Map<String, Object> model )
425     {
426         List<ErrorMessage> listErrors = new ArrayList<ErrorMessage>( _listErrors );
427         List<ErrorMessage> listInfos = new ArrayList<ErrorMessage>( _listInfos );
428         model.put( MARK_ERRORS, listErrors );
429         model.put( MARK_INFOS, listInfos );
430         _listErrors.clear(  );
431         _listInfos.clear(  );
432     }
433 
434     ////////////////////////////////////////////////////////////////////////////
435     // Redirect utils
436 
437     /**
438      * Redirect to requested page
439      *
440      * @param request the http request
441      * @param strTarget the targeted page
442      * @return the page requested
443      */
444     protected XPage redirect( HttpServletRequest request, String strTarget )
445     {
446         HttpServletResponse response = LocalVariables.getResponse(  );
447 
448         try
449         {
450             _logger.debug( "Redirect :" + strTarget );
451             response.sendRedirect( strTarget );
452         }
453         catch ( IOException e )
454         {
455             _logger.error( "Unable to redirect : " + strTarget + " : " + e.getMessage(  ), e );
456         }
457 
458         return new XPage(  );
459     }
460 
461     /**
462      * Redirect to an url defined by given parameters
463      * @param request The HTTP request
464      * @param strView The View name
465      * @param strParameter The additional parameter
466      * @param nValue The additional parameter's value
467      * @return The XPage redirected
468      */
469     protected XPage redirect( HttpServletRequest request, String strView, String strParameter, int nValue )
470     {
471         UrlItem url = new UrlItem( getViewUrl( strView ) );
472         url.addParameter( strParameter, nValue );
473 
474         return redirect( request, url.getUrl(  ) );
475     }
476 
477     /**
478      * Redirect to an url defined by given parameters
479      * @param request The HTTP request
480      * @param strView The View name
481      * @param strParameter1 The first additional parameter
482      * @param nValue1 The first additional parameter's value
483      * @param strParameter2 The second additionnal parameter
484      * @param nValue2 The second additionnal parameter's value
485      * @return The XPage redirected
486      */
487     protected XPage redirect( HttpServletRequest request, String strView, String strParameter1, int nValue1,
488         String strParameter2, int nValue2 )
489     {
490         UrlItem url = new UrlItem( getViewUrl( strView ) );
491         url.addParameter( strParameter1, nValue1 );
492         url.addParameter( strParameter2, nValue2 );
493 
494         return redirect( request, url.getUrl(  ) );
495     }
496 
497     /**
498      * Redirect to an url defined by given parameters
499      * @param request The HTTP request
500      * @param strView The View name
501      * @param additionalParameters A map containing parameters to add to the
502      *            URL. Keys of the map are parameters name, and values are
503      *            parameters values
504      * @return The XPage redirected
505      */
506     protected XPage redirect( HttpServletRequest request, String strView, Map<String, String> additionalParameters )
507     {
508         UrlItem url = new UrlItem( getViewUrl( strView ) );
509 
510         if ( additionalParameters != null )
511         {
512             for ( Entry<String, String> entry : additionalParameters.entrySet(  ) )
513             {
514                 url.addParameter( entry.getKey(  ), entry.getValue(  ) );
515             }
516         }
517 
518         return redirect( request, url.getUrl(  ) );
519     }
520 
521     /**
522      * Redirect to requested view
523      *
524      * @param request the http request
525      * @param strView the targeted view
526      * @return the page requested
527      */
528     protected XPage redirectView( HttpServletRequest request, String strView )
529     {
530         return redirect( request, getViewUrl( strView ) );
531     }
532 
533     /**
534      * Get a View URL
535      * @param strView The view name
536      * @return The URL
537      */
538     protected String getViewUrl( String strView )
539     {
540         UrlItem url = new UrlItem( URL_PORTAL );
541         url.addParameter( MVCUtils.PARAMETER_PAGE, getXPageName(  ) );
542         url.addParameter( MVCUtils.PARAMETER_VIEW, strView );
543 
544         return url.getUrl(  );
545     }
546 
547     /**
548      * Gets the view URL with the JSP path
549      * @param strView The view
550      * @return The URL
551      */
552     protected String getViewFullUrl( String strView )
553     {
554         return PATH_PORTAL + getViewUrl( strView );
555     }
556 
557     /**
558      * Get Action URL
559      * @param strAction The view name
560      * @return The URL
561      */
562     protected String getActionUrl( String strAction )
563     {
564         UrlItem url = new UrlItem( URL_PORTAL );
565         url.addParameter( MVCUtils.PARAMETER_PAGE, getXPageName(  ) );
566         url.addParameter( MVCUtils.PARAMETER_ACTION, strAction );
567 
568         return url.getUrl(  );
569     }
570 
571     /**
572      * Get Action URL
573      * @param strAction The view name
574      * @return The URL
575      */
576     protected String getActionFullUrl( String strAction )
577     {
578         return PATH_PORTAL + getActionUrl( strAction );
579     }
580 
581     /**
582      * Initiates a file download
583      * @param strData Data of the file to download
584      * @param strFilename Name of file
585      * @param strContentType content type to set to the response
586      * @return The page requested
587      */
588     protected XPage download( String strData, String strFilename, String strContentType )
589     {
590         HttpServletResponse response = LocalVariables.getResponse(  );
591         PrintWriter out = null;
592         response.setHeader( "Content-Disposition", "attachment; filename=\"" + strFilename + "\";" );
593         MVCUtils.addDownloadHeaderToResponse( response, strFilename, strContentType );
594 
595         try
596         {
597             out = response.getWriter(  );
598             out.print( strData );
599         }
600         catch ( IOException e )
601         {
602             AppLogService.error( e.getStackTrace(  ), e );
603         }
604         finally
605         {
606             if ( out != null )
607             {
608                 out.close(  );
609             }
610         }
611 
612         return new XPage(  );
613     }
614 
615     /**
616      * Initiates a download of a byte array
617      * @param data Data to download
618      * @param strFilename Name of the downloaded file
619      * @param strContentType Content type to set to the response
620      * @return The page requested
621      */
622     protected XPage download( byte[] data, String strFilename, String strContentType )
623     {
624         HttpServletResponse response = LocalVariables.getResponse(  );
625         OutputStream os;
626         MVCUtils.addDownloadHeaderToResponse( response, strFilename, strContentType );
627 
628         try
629         {
630             os = response.getOutputStream(  );
631             os.write( data );
632             os.close(  );
633         }
634         catch ( IOException e )
635         {
636             AppLogService.error( e.getStackTrace(  ), e );
637         }
638 
639         return new XPage(  );
640     }
641 
642     /**
643      * Return a response as JSON content
644      * @param strJSON The JSON
645      * @return An unused XPage
646      */
647     protected XPage responseJSON( String strJSON )
648     {
649         HttpServletResponse response = LocalVariables.getResponse(  );
650         response.setContentType( CONTENT_TYPE_JSON );
651 
652         try
653         {
654             PrintWriter out = response.getWriter(  );
655             out.print( strJSON );
656             out.flush(  );
657             out.close(  );
658         }
659         catch ( IOException e )
660         {
661             AppLogService.error( e.getStackTrace(  ), e );
662         }
663 
664         return new XPage(  );
665     }
666 
667     /**
668      * Return a response as XML content
669      * @param strXML The XML
670      * @return An unused XPage
671      */
672     protected XPage responseXML( String strXML )
673     {
674         HttpServletResponse response = LocalVariables.getResponse(  );
675         response.setContentType( CONTENT_TYPE_XML );
676 
677         try
678         {
679             PrintWriter out = response.getWriter(  );
680             out.print( strXML );
681             out.flush(  );
682             out.close(  );
683         }
684         catch ( IOException e )
685         {
686             AppLogService.error( e.getStackTrace(  ), e );
687         }
688 
689         return new XPage(  );
690     }
691 
692     ////////////////////////////////////////////////////////////////////////////
693     // MESSAGE BOX MANAGEMENT
694 
695     /**
696      * Redirect to a Message Box page
697      * @param request The HTTP request
698      * @param messageBox The MessageBox infos
699      * @return A redirect XPage
700      */
701     protected XPage redirectMessageBox( HttpServletRequest request, MVCMessageBox messageBox )
702     {
703         _messageBox = messageBox;
704 
705         return redirectView( request, VIEW_MESSAGEBOX );
706     }
707 
708     /**
709      * Check if a message box is asked for
710      * @param request The HTTP request
711      * @return If a message box is asked
712      */
713     private boolean isMessageBox( HttpServletRequest request )
714     {
715         String strView = request.getParameter( MVCUtils.PARAMETER_VIEW );
716 
717         return ( ( strView != null ) && ( strView.equals( VIEW_MESSAGEBOX ) ) );
718     }
719 
720     /**
721      * Default getLocale() implementation. Could be overriden
722      * @param request The HTTP request
723      * @return The Locale
724      */
725     protected Locale getLocale( HttpServletRequest request )
726     {
727         return request.getLocale(  );
728     }
729 
730     /**
731      * Display the Message BOX
732      * @param request The HTTP request
733      * @return The message box
734      */
735     private XPage messageBox( HttpServletRequest request )
736     {
737         _messageBox.localize( getLocale( request ) );
738 
739         Map<String, Object> model = getModel(  );
740         model.put( MARK_MESSAGE_BOX, _messageBox );
741 
742         return getXPage( _messageBox.getTemplate(  ), getLocale( request ), model );
743     }
744 }