View Javadoc
1   /*
2    * Copyright (c) 2002-2017, 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.plugins.webappcontainer.web;
35  
36  import java.util.Collection;
37  import java.util.HashMap;
38  import java.util.Map;
39  
40  import javax.servlet.http.HttpServletRequest;
41  
42  import org.apache.log4j.Logger;
43  
44  import fr.paris.lutece.plugins.webappcontainer.business.Site;
45  import fr.paris.lutece.plugins.webappcontainer.business.SiteHome;
46  import fr.paris.lutece.plugins.webappcontainer.business.WebappResponse;
47  import fr.paris.lutece.plugins.webappcontainer.service.WebappcontainerPlugin;
48  import fr.paris.lutece.plugins.webappcontainer.util.HtmlDocumentWebappcontainer;
49  import fr.paris.lutece.plugins.webappcontainer.util.HtmlDocumentWebappcontainerException;
50  import fr.paris.lutece.plugins.webappcontainer.util.HttpAccessException;
51  import fr.paris.lutece.plugins.webappcontainer.util.UrlUtils;
52  import fr.paris.lutece.portal.service.i18n.I18nService;
53  import fr.paris.lutece.portal.service.message.SiteMessage;
54  import fr.paris.lutece.portal.service.message.SiteMessageException;
55  import fr.paris.lutece.portal.service.message.SiteMessageService;
56  import fr.paris.lutece.portal.service.plugin.Plugin;
57  import fr.paris.lutece.portal.service.plugin.PluginService;
58  import fr.paris.lutece.portal.service.security.UserNotSignedException;
59  import fr.paris.lutece.portal.service.template.AppTemplateService;
60  import fr.paris.lutece.portal.service.util.AppLogService;
61  import fr.paris.lutece.portal.service.util.AppPathService;
62  import fr.paris.lutece.portal.service.util.AppPropertiesService;
63  import fr.paris.lutece.portal.web.xpages.XPage;
64  import fr.paris.lutece.portal.web.xpages.XPageApplication;
65  import fr.paris.lutece.util.html.HtmlTemplate;
66  import fr.paris.lutece.util.url.UrlItem;
67  
68  /**
69   * 
70   */
71  public class WebappcontainerApp implements XPageApplication
72  {
73      // Parameters
74      public static final String PARAMETER_CODE = "site_code";
75      public static final String PARAMETER_WEBAPP_URL = "webapp_url";
76      public static final String PARAMETER_PAGE = "page";
77      public static final String PARAMETER_PAGE_HACK = "page_external_site_";
78  
79      // Templates
80      private static final String TEMPLATE_SITES = "skin/plugins/webappcontainer/list_sites.html";
81      private static final String TEMPLATE_SITE_CONTENT = "skin/plugins/webappcontainer/site_content.html";
82  
83      // Others
84      private static final String EMPTY_STRING = "";
85      private static final String SERVLET_WEBAPPCONTAINER = "webappcontainer";
86  
87      // Markers
88      private static final String MARK_SITES_LIST = "sites_list";
89      private static final String MARK_SITE = "site";
90      private static final String MARK_WEBAPP_URL = "webapp_url";
91      private static final String MARK_EXTERNAL_SITE_BODY = "external_site_body";
92  
93      // Properties
94      private static final String PROPERTY_INCLUDE_CSS = "webappcontainer.rebuildHtmlPage.includeCSS";
95  
96      // I18n
97      private static final String PROPERTY_PAGE_TITLE_SITES = "webappcontainer.list_sites.pageTitle";
98      private static final String PROPERTY_PAGE_PATH_LABEL = "webappcontainer.list_sites.pagePathLabel";
99      private static final String MESSAGE_ERROR_SITE = "webappcontainer.message.errorGettingSite";
100     private static final String MESSAGE_ERROR_CONTENT = "webappcontainer.message.errorParsingDom";
101 
102     // Logger
103     private static Logger _logger;
104     private static final String LOGGER_DEBUG_WEBAPPCONTAINER = "lutece.debug.webappcontainer";
105 
106     /**
107      * Returns the XPage content depending on the request parameters and the current mode.
108      * 
109      * @param request The HTTP request.
110      * @param nMode The current mode.
111      * @param plugin The plugin
112      * @return The page content.
113      * @throws UserNotSignedException If the application ask for an user sign
114      * @throws SiteMessageException occurs when a site message need to be displayed
115      */
116     public XPage getPage( HttpServletRequest request, int nMode, Plugin plugin ) throws UserNotSignedException,
117             SiteMessageException
118     {
119         String strCode = request.getParameter( PARAMETER_CODE );
120 
121         if ( _logger == null )
122         {
123             _logger = Logger.getLogger( LOGGER_DEBUG_WEBAPPCONTAINER );
124         }
125 
126         if ( (strCode == null) || strCode.equals( EMPTY_STRING ) )
127         {
128             return getListSites( request, nMode, plugin );
129         }
130         else
131         {
132             return getWebappContainerFromSiteCode( strCode, request, nMode, plugin );
133         }
134     }
135 
136     /**
137      * Get the {@link Site} object in Session or create it if not present
138      * 
139      * @param request The {@link HttpServletRequest}
140      * @return The {@link Site} object
141      */
142     public static Site getSite( HttpServletRequest request )
143     {
144         Site site = null;
145         String strCode = request.getParameter( PARAMETER_CODE );
146 
147         if ( strCode != null )
148         {
149             site = SiteHome.findByPrimaryKey( strCode, PluginService.getPlugin( WebappcontainerPlugin.PLUGIN_NAME ) );
150         }
151 
152         return site;
153     }
154 
155     /**
156      * Get the requested url in Session or create it if not present
157      * 
158      * @param request The {@link HttpServletRequest}
159      * @return The requested url
160      */
161     public static String getRequestUrl( HttpServletRequest request )
162     {
163         String strUrl = request.getParameter( PARAMETER_WEBAPP_URL );
164 
165         return UrlUtils.decodeUrl( strUrl );
166     }
167 
168     /**
169      * Get the list of external sites
170      * 
171      * @param request The {@link HttpServletRequest}
172      * @param nMode The mode
173      * @param plugin The {@link Plugin}
174      * @return The Xpage with the list of accessible externals sites
175      * @throws UserNotSignedException If used is not signed
176      * @throws SiteMessageException Display a Site message
177      */
178     private XPage getListSites( HttpServletRequest request, int nMode, Plugin plugin ) throws UserNotSignedException,
179             SiteMessageException
180     {
181         XPage page = new XPage( );
182         page.setTitle( I18nService.getLocalizedString( PROPERTY_PAGE_TITLE_SITES, request.getLocale( ) ) );
183         page.setPathLabel( I18nService.getLocalizedString( PROPERTY_PAGE_PATH_LABEL, request.getLocale( ) ) );
184 
185         Collection<Site> listSite = SiteHome.findAll( plugin );
186         Map<String, Object> model = new HashMap<String, Object>( );
187         model.put( MARK_SITES_LIST, listSite );
188 
189         HtmlTemplate templateList = AppTemplateService.getTemplate( TEMPLATE_SITES, request.getLocale( ), model );
190         page.setContent( templateList.getHtml( ) );
191 
192         return page;
193     }
194 
195     /**
196      * Get the {@link Site} Content
197      * 
198      * @param strCode The {@link Site} code
199      * @param request The {@link HttpServletRequest}
200      * @param nMode The mode
201      * @param plugin The {@link Plugin}
202      * @return The Xpage containing the {@link Site} content
203      * @throws UserNotSignedException If used is not signed
204      * @throws SiteMessageException Display a Site message
205      */
206     private XPage getWebappContainerFromSiteCode( String strCode, HttpServletRequest request, int nMode, Plugin plugin )
207             throws UserNotSignedException, SiteMessageException
208     {
209         XPage page = new XPage( );
210         Map<String, Object> model = new HashMap<String, Object>( );
211         StringBuffer strWebappTextContent = null;
212         WebappResponse webappResponse = null;
213         String strRequestedUrl = WebappcontainerApp.getRequestUrl( request );
214         Site site = WebappcontainerApp.getSite( request );
215 
216         if ( (strRequestedUrl == null) || strRequestedUrl.equals( EMPTY_STRING ) )
217         {
218             strRequestedUrl = site.getUrl( );
219         }
220 
221         // Get the webapp content with HTTPAccess
222         try
223         {
224             webappResponse = WebappcontainerResourceServlet.getWebappResponse( request, strRequestedUrl, site );
225         }
226         catch ( HttpAccessException e )
227         {
228             SiteMessageService.setMessage( request, MESSAGE_ERROR_SITE, new String[] { e.getMessage( ) },
229                     SiteMessage.TYPE_ERROR );
230             AppLogService.error( "Error when retrieving external site content (site : " + site.getCode( ) + ", url : "
231                     + strRequestedUrl + ")", e );
232         }
233 
234         // Proces the webapp content modifications
235         if ( webappResponse != null )
236         {
237             strRequestedUrl = webappResponse.getLocation( );
238 
239             strWebappTextContent = getWebappBody( request, webappResponse.getContent( ), webappResponse
240                     .getContentCharset( ), strRequestedUrl, site );
241         }
242 
243         // Set the template model, set the XPage
244         page.setTitle( site.getDescription( ) );
245         page.setPathLabel( site.getCode( ) );
246         // _logger.debug( strWebappTextContent );
247         model.put( MARK_EXTERNAL_SITE_BODY, strWebappTextContent );
248         model.put( MARK_SITE, site );
249         model.put( MARK_WEBAPP_URL, strRequestedUrl );
250 
251         HtmlTemplate templateList = AppTemplateService.getTemplate( TEMPLATE_SITE_CONTENT, request.getLocale( ), model );
252         page.setContent( templateList.getHtml( ) );
253 
254         return page;
255     }
256 
257     /**
258      * Process the modifications of webapp HTML code (rewrite Url, get only BODY and HEAD tags)
259      * 
260      * @param request The {@link HttpServletRequest}
261      * @param bytesWebappTextContent The source webapp text content
262      * @param strEncoding The encoding used to get the webapp body
263      * @param strRequestedUrl The requested URL
264      * @param site The site
265      * @return a {@link StringBuffer} with the converted HTML code
266      * @throws UserNotSignedException If used is not signed
267      * @throws SiteMessageException Display a Site message
268      */
269     private static StringBuffer getWebappBody( HttpServletRequest request, byte[] bytesWebappTextContent,
270             String strEncoding, String strRequestedUrl, Site site ) throws UserNotSignedException, SiteMessageException
271     {
272         HtmlDocumentWebappcontainer doc = null;
273 
274         try
275         {
276             doc = new HtmlDocumentWebappcontainer( bytesWebappTextContent, strEncoding );
277         }
278         catch ( HtmlDocumentWebappcontainerException e )
279         {
280             AppLogService.error( "Error when parsing DOM HTML (site : " + site.getCode( ) + ", url : "
281                     + strRequestedUrl + ")", e );
282             SiteMessageService.setMessage( request, MESSAGE_ERROR_SITE, new String[] { I18nService.getLocalizedString(
283                     MESSAGE_ERROR_CONTENT, request.getLocale( ) ) }, SiteMessage.TYPE_ERROR );
284         }
285 
286         // Get the base Url
287         String strBaseUrl = getBaseUrl( doc );
288         String strBaseUrlWebapp = strRequestedUrl;
289 
290         if ( (strBaseUrl != null) && !strBaseUrl.equals( EMPTY_STRING ) )
291         {
292             strBaseUrlWebapp = strBaseUrl;
293 
294             removeBaseUrl( doc );
295         }
296 
297         rewriteUrls( doc, getXpageUrl( site.getCode( ) ), getServletUrl( site.getCode( ) ), strBaseUrlWebapp, site );
298 
299         StringBuffer sbHTMLContent;
300 
301         if ( site.isRebuildHtmlPage( ) )
302         {
303             sbHTMLContent = getHead( doc );
304             sbHTMLContent.append( getBody( doc ) );
305         }
306         else
307         {
308             sbHTMLContent = doc.getContent( );
309         }
310 
311         return sbHTMLContent;
312     }
313 
314     /**
315      * Get the webappcontainer XPage url
316      * 
317      * @param strCode The site code
318      * @return The XPage Url
319      */
320     private static String getXpageUrl( String strCode )
321     {
322         UrlItem url = null;
323 
324         url = new UrlItem( AppPathService.getPortalUrl( ) );
325         url.addParameter( "page", "webappcontainer" );
326 
327         url.addParameter( PARAMETER_CODE, strCode );
328         url.addParameter( PARAMETER_WEBAPP_URL, EMPTY_STRING );
329 
330         return url.getUrl( );
331     }
332 
333     /**
334      * Get the webappcontainer servlet
335      * 
336      * @param strCode The site code
337      * @return The servlet Url
338      */
339     private static String getServletUrl( String strCode )
340     {
341         UrlItem prefixUrl = new UrlItem( SERVLET_WEBAPPCONTAINER );
342         prefixUrl.addParameter( PARAMETER_CODE, strCode );
343         prefixUrl.addParameter( PARAMETER_WEBAPP_URL, EMPTY_STRING );
344 
345         return prefixUrl.getUrl( );
346     }
347 
348     /**
349      * Rewrite the webapp urls : Replace all url by :
350      * <ul>
351      * <li>for img, css, javascript, rss tags : a link to webappcontainer servlet (if option enabled)</li>
352      * <li>for a, form tags : a link to webappcontainer XPage</li>
353      * </ul>
354      * The old url will be set :
355      * <ul>
356      * <li>for simple links : in GET parameter (in url)
357      * <li>for forms : as input type "hidden"
358      * </ul>
359      * 
360      * @param doc HtmlDocumentWebappcontainer document
361      * @param strXpageUrl The XPage url
362      * @param strServletUrl The webappcontainer servlet Url
363      * @param strBaseUrlWebapp The base url of webapp
364      * @param site The {@link Site} object
365      */
366     private static void rewriteUrls( HtmlDocumentWebappcontainer doc, String strXpageUrl, String strServletUrl,
367             String strBaseUrlWebapp, Site site )
368     {
369         boolean bEncodeUrlFormNonHtmlContent = true;
370         String strBaseUrlWebappForHtmlContent = strServletUrl;
371 
372         if ( !site.isRedirectNonHtmlContent( ) )
373         {
374             // If no redirection, the non HTML content urls must not be encoded. They just have to be convert to absolute urls.
375             bEncodeUrlFormNonHtmlContent = false;
376             strBaseUrlWebappForHtmlContent = EMPTY_STRING;
377         }
378 
379         doc.convertUrls( HtmlDocumentWebappcontainer.ELEMENT_IMG, strBaseUrlWebapp, site,
380                 strBaseUrlWebappForHtmlContent, bEncodeUrlFormNonHtmlContent );
381         doc.convertUrls( HtmlDocumentWebappcontainer.ELEMENT_CSS_LINK, strBaseUrlWebapp, site,
382                 strBaseUrlWebappForHtmlContent, bEncodeUrlFormNonHtmlContent );
383         doc.convertUrls( HtmlDocumentWebappcontainer.ELEMENT_ALTERNATE, strBaseUrlWebapp, site,
384                 strBaseUrlWebappForHtmlContent, bEncodeUrlFormNonHtmlContent );
385         doc.convertUrls( HtmlDocumentWebappcontainer.ELEMENT_JAVASCRIPT, strBaseUrlWebapp, site,
386                 strBaseUrlWebappForHtmlContent, bEncodeUrlFormNonHtmlContent );
387 
388         doc.convertUrls( HtmlDocumentWebappcontainer.ELEMENT_A, strBaseUrlWebapp, site, strXpageUrl, true );
389         doc.convertUrls( HtmlDocumentWebappcontainer.ELEMENT_FORM, strBaseUrlWebapp, site, strXpageUrl, true );
390     }
391 
392     /**
393      * Get the content of "head" HTML tag
394      * 
395      * @param doc The HtmlDocumentWebappcontainer document
396      * @return a {@link StringBuffer} with the head content
397      */
398     private static StringBuffer getHead( HtmlDocumentWebappcontainer doc )
399     {
400         StringBuffer sbHeadContent = new StringBuffer( );
401 
402         // Get scripts
403         sbHeadContent.append( doc.getElements( HtmlDocumentWebappcontainer.ELEMENT_JAVASCRIPT ) );
404 
405         boolean bIncludeCSS = Boolean.parseBoolean( AppPropertiesService.getProperty( PROPERTY_INCLUDE_CSS, Boolean
406                 .toString( false ) ) );
407 
408         if ( bIncludeCSS )
409         {
410             // Get styles
411             sbHeadContent.append( doc.getElements( HtmlDocumentWebappcontainer.ELEMENT_CSS_LINK ) );
412             sbHeadContent.append( doc.getElements( HtmlDocumentWebappcontainer.ELEMENT_CSS_STYLE ) );
413         }
414 
415         return sbHeadContent;
416     }
417 
418     /**
419      * Get the content of "body" HTML tag
420      * 
421      * @param doc The HtmlDocumentWebappcontainer document
422      * @return a {@link StringBuffer} with the body content
423      */
424     private static StringBuffer getBody( HtmlDocumentWebappcontainer doc )
425     {
426         return doc.getFirstElement( HtmlDocumentWebappcontainer.ELEMENT_BODY );
427     }
428 
429     /**
430      * Get the content of "href" attribute of "base" HTML tag
431      * 
432      * @param doc The HtmlDocumentWebappcontainer document
433      * @return the site base url or null
434      */
435     private static String getBaseUrl( HtmlDocumentWebappcontainer doc )
436     {
437         return doc.getFirstElementAttribute( HtmlDocumentWebappcontainer.ELEMENT_BASE );
438     }
439 
440     /**
441      * Remove the "base" tag
442      * 
443      * @param doc The HtmlDocumentWebappcontainer document
444      */
445     private static void removeBaseUrl( HtmlDocumentWebappcontainer doc )
446     {
447         doc.removeFirstElement( HtmlDocumentWebappcontainer.ELEMENT_BASE );
448     }
449 }