View Javadoc
1   /*
2    * Copyright (c) 2002-2022, City of 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.web.system;
35  
36  import fr.paris.lutece.portal.business.portlet.PortletType;
37  import fr.paris.lutece.portal.business.portlet.PortletTypeHome;
38  import fr.paris.lutece.portal.service.admin.AccessDeniedException;
39  import fr.paris.lutece.portal.service.admin.AdminUserService;
40  import fr.paris.lutece.portal.service.database.AppConnectionService;
41  import fr.paris.lutece.portal.service.i18n.I18nService;
42  import fr.paris.lutece.portal.service.init.AppInfo;
43  import fr.paris.lutece.portal.service.message.AdminMessage;
44  import fr.paris.lutece.portal.service.message.AdminMessageService;
45  import fr.paris.lutece.portal.service.plugin.Plugin;
46  import fr.paris.lutece.portal.service.plugin.PluginService;
47  import fr.paris.lutece.portal.service.security.SecurityTokenService;
48  import fr.paris.lutece.portal.service.template.AppTemplateService;
49  import fr.paris.lutece.portal.service.util.AppLogService;
50  import fr.paris.lutece.portal.web.admin.AdminFeaturesPageJspBean;
51  import fr.paris.lutece.util.ReferenceList;
52  import fr.paris.lutece.util.html.HtmlTemplate;
53  
54  import java.util.ArrayList;
55  import java.util.Collection;
56  import java.util.HashMap;
57  import java.util.Locale;
58  import java.util.Map;
59  
60  import javax.servlet.ServletContext;
61  import javax.servlet.http.HttpServletRequest;
62  
63  /**
64   * This class provides the user interface to manage the lutece plugins (install, enable, disable)
65   */
66  public class PluginJspBean extends AdminFeaturesPageJspBean
67  {
68      // //////////////////////////////////////////////////////////////////////////////
69      // Constants
70      public static final String RIGHT_MANAGE_PLUGINS = "CORE_PLUGINS_MANAGEMENT";
71      private static final long serialVersionUID = -9058113426951331118L;
72      private static final String TEMPLATE_MANAGE_PLUGINS = "admin/system/manage_plugins.html";
73      private static final String MARK_PLUGINS_LIST = "plugins_list";
74      private static final String MARK_CORE = "core";
75      private static final String MARK_POOLS_LIST = "pools_list";
76      private static final String MARK_FILTER_LIST = "filter_list";
77      private static final String MARK_CURRENT_FILTER = "current_filter";
78      private static final String PROPERTY_PLUGIN_MESSAGE = "portal.system.message.confirmDisable";
79      private static final String PROPERTY_PLUGIN_PORTLET_EXIST_MESSAGE = "portal.system.message.portletExist";
80      private static final String PROPERTY_PLUGIN_NO_CORE_COMPATIBILITY_MESSAGE = "portal.system.message.noCoreCompatibility";
81      private static final String PROPERTY_PLUGIN_INSTALL_ERROR = "portal.system.message.installError";
82      private static final String PARAM_PLUGIN_NAME = "plugin_name";
83      private static final String PARAM_PLUGIN_TYPE = "plugin_type";
84      private static final String PARAM_DB_POOL_NAME = "db_pool_name";
85      private static final String PARAM_PLUGIN_TYPE_ALL = "all";
86      private static final String PARAM_PLUGIN_TYPE_PORTLET = "portlet";
87      private static final String PARAM_PLUGIN_TYPE_APPLICATION = "application";
88      private static final String PARAM_PLUGIN_TYPE_FEATURE = "feature";
89      private static final String PARAM_PLUGIN_TYPE_INSERTSERVICE = "insertservice";
90      private static final String PARAM_PLUGIN_TYPE_CONTENTSERVICE = "contentservice";
91      private static final String PROPERTY_PLUGIN_TYPE_NAME_ALL = "portal.system.pluginType.name.all";
92      private static final String PROPERTY_PLUGIN_TYPE_NAME_APPLICATION = "portal.system.pluginType.name.application";
93      private static final String PROPERTY_PLUGIN_TYPE_NAME_PORTLET = "portal.system.pluginType.name.portlet";
94      private static final String PROPERTY_PLUGIN_TYPE_NAME_FEATURE = "portal.system.pluginType.name.feature";
95      private static final String PROPERTY_PLUGIN_TYPE_NAME_INSERTSERVICE = "portal.system.pluginType.name.insertService";
96      private static final String PROPERTY_PLUGIN_TYPE_NAME_CONTENTSERVICE = "portal.system.pluginType.name.contentService";
97      private static final String TEMPLATE_PLUGIN_DETAILS = "/admin/system/view_plugin.html";
98      private static final String JSP_UNINSTALL_PLUGIN = "jsp/admin/system/DoUninstallPlugin.jsp";
99  
100     /**
101      * Returns the plugins management page
102      *
103      * @param request
104      *            The Http request
105      * @return Html page
106      */
107     public String getManagePlugins( HttpServletRequest request )
108     {
109         Locale locale = AdminUserService.getLocale( request );
110         String strPluginTypeFilter = request.getParameter( PARAM_PLUGIN_TYPE );
111         Collection<Plugin> listPlugins = PluginService.getPluginList( );
112         HashMap<String, Object> model = new HashMap<>( );
113         model.put( MARK_PLUGINS_LIST, filterPluginsList( listPlugins, strPluginTypeFilter ) );
114         model.put( MARK_CORE, PluginService.getCore( ) );
115         model.put( MARK_POOLS_LIST, getPoolsList( ) );
116         model.put( MARK_FILTER_LIST, getPluginTypeFilterList( locale ) );
117         model.put( MARK_CURRENT_FILTER, ( strPluginTypeFilter != null ) ? strPluginTypeFilter : "" );
118         model.put( SecurityTokenService.MARK_TOKEN, SecurityTokenService.getInstance( ).getToken( request, TEMPLATE_MANAGE_PLUGINS ) );
119 
120         HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_MANAGE_PLUGINS, locale, model );
121 
122         return getAdminPage( template.getHtml( ) );
123     }
124 
125     /**
126      * Install a plugin
127      *
128      * @param request
129      *            The Http request
130      * @param context
131      *            The servlet context
132      * @return the url of the page containing a log essage
133      * @throws AccessDeniedException
134      *             if the security token is invalid
135      */
136     public String doInstallPlugin( HttpServletRequest request, ServletContext context ) throws AccessDeniedException
137     {
138         String strPluginName = request.getParameter( PARAM_PLUGIN_NAME );
139         Plugin plugin = PluginService.getPlugin( strPluginName );
140 
141         if ( !verifyCoreCompatibility( plugin ) )
142         {
143             Object [ ] args = {
144                     plugin.getMinCoreVersion( ), plugin.getMaxCoreVersion( )
145             };
146 
147             return AdminMessageService.getMessageUrl( request, PROPERTY_PLUGIN_NO_CORE_COMPATIBILITY_MESSAGE, args, AdminMessage.TYPE_STOP );
148 
149         }
150         if ( !SecurityTokenService.getInstance( ).validate( request, TEMPLATE_MANAGE_PLUGINS ) )
151         {
152             throw new AccessDeniedException( ERROR_INVALID_TOKEN );
153         }
154         try
155         {
156             plugin.install( );
157         }
158         catch( Exception e )
159         {
160             AppLogService.error( e.getMessage( ), e );
161 
162             return AdminMessageService.getMessageUrl( request, PROPERTY_PLUGIN_INSTALL_ERROR, AdminMessage.TYPE_STOP );
163         }
164 
165         return getHomeUrl( request );
166     }
167 
168     /**
169      * Uninstall a plugin
170      *
171      * @param request
172      *            The Http request
173      * @param context
174      *            The servlet context
175      * @return the url of the page containing a log essage
176      * @throws AccessDeniedException
177      *             if the security token is invalid
178      */
179     public String doUninstallPlugin( HttpServletRequest request, ServletContext context ) throws AccessDeniedException
180     {
181         if ( !SecurityTokenService.getInstance( ).validate( request, JSP_UNINSTALL_PLUGIN ) )
182         {
183             throw new AccessDeniedException( ERROR_INVALID_TOKEN );
184         }
185         try
186         {
187             String strPluginName = request.getParameter( PARAM_PLUGIN_NAME );
188             Plugin plugin = PluginService.getPlugin( strPluginName );
189             plugin.uninstall( );
190         }
191         catch( Exception e )
192         {
193             AppLogService.error( e.getMessage( ), e );
194         }
195 
196         return getHomeUrl( request );
197     }
198 
199     /**
200      * Returns the page of confirmation for uninstalling a plugin
201      *
202      * @param request
203      *            The Http Request
204      * @return the HTML page
205      */
206     public String getConfirmUninstallPlugin( HttpServletRequest request )
207     {
208         String strPluginName = request.getParameter( PARAM_PLUGIN_NAME );
209         Plugin plugin = PluginService.getPlugin( strPluginName );
210         Collection<PortletType> listPortletTypes = plugin.getPortletTypes( );
211         String strMessageKey = PROPERTY_PLUGIN_MESSAGE;
212         Map<String, String> parameters = new HashMap<>( );
213         parameters.put( PARAM_PLUGIN_NAME, strPluginName );
214         parameters.put( SecurityTokenService.PARAMETER_TOKEN, SecurityTokenService.getInstance( ).getToken( request, JSP_UNINSTALL_PLUGIN ) );
215         String strAdminMessageUrl = AdminMessageService.getMessageUrl( request, strMessageKey, JSP_UNINSTALL_PLUGIN, AdminMessage.TYPE_CONFIRMATION,
216                 parameters );
217 
218         for ( PortletType portletType : listPortletTypes )
219         {
220             String strPluginHomeClass = portletType.getHomeClass( );
221 
222             if ( ( plugin.getType( ) & Plugin.PLUGIN_TYPE_PORTLET ) != 0 && isPortletExists( strPluginHomeClass ) )
223             {
224                 strMessageKey = PROPERTY_PLUGIN_PORTLET_EXIST_MESSAGE;
225                 strAdminMessageUrl = AdminMessageService.getMessageUrl( request, strMessageKey, AdminMessage.TYPE_CONFIRMATION );
226             }
227         }
228 
229         return strAdminMessageUrl;
230     }
231 
232     /**
233      * Defines the database connection pool to be used by the plugin
234      * 
235      * @param request
236      *            The http request
237      * @return the URL to redirect after this action
238      * @throws AccessDeniedException
239      *             if the security token is invalid
240      */
241     public String doModifyPluginPool( HttpServletRequest request ) throws AccessDeniedException
242     {
243         if ( !SecurityTokenService.getInstance( ).validate( request, TEMPLATE_MANAGE_PLUGINS ) )
244         {
245             throw new AccessDeniedException( ERROR_INVALID_TOKEN );
246         }
247         String strPluginName = request.getParameter( PARAM_PLUGIN_NAME );
248         String strDbPoolName = request.getParameter( PARAM_DB_POOL_NAME );
249 
250         try
251         {
252             Plugin plugin = PluginService.getPlugin( strPluginName );
253             plugin.updatePoolName( strDbPoolName );
254         }
255         catch( Exception e )
256         {
257             AppLogService.error( e.getMessage( ), e );
258         }
259 
260         return getHomeUrl( request );
261     }
262 
263     /**
264      * Displays a plugin's description
265      * 
266      * @param request
267      *            The HTTP request
268      * @return The popup HTML code
269      */
270     public String getPluginDescription( HttpServletRequest request )
271     {
272         String strPluginName = request.getParameter( PARAM_PLUGIN_NAME );
273         Plugin plugin;
274         if ( PluginService.getCore( ).getName( ).equals( strPluginName ) )
275         {
276             plugin = PluginService.getCore( );
277         }
278         else
279         {
280             plugin = PluginService.getPlugin( strPluginName );
281         }
282 
283         // set the locale for the feature labels
284         I18nService.localizeCollection( plugin.getRights( ), getLocale( ) );
285         // set the locale for the portlet types labels
286         I18nService.localizeCollection( plugin.getPortletTypes( ), getLocale( ) );
287         // set the locale for the link services labels
288         I18nService.localizeCollection( plugin.getInsertServices( ), getLocale( ) );
289 
290         HtmlTemplate template = AppTemplateService.getTemplate( TEMPLATE_PLUGIN_DETAILS, getLocale( ), plugin );
291 
292         return getAdminPage( template.getHtml( ) );
293     }
294 
295     // ////////////////////////////////////////////////////////////////////////////////////////////////////////
296     // Private implementation
297 
298     /**
299      * Return a filter list of plugins
300      * 
301      * @param listPlugins
302      *            the COllection of plugins
303      * @param strPluginTypeFilter
304      *            The filter
305      * @return list The list of plugins
306      */
307     private Collection<Plugin> filterPluginsList( Collection<Plugin> listPlugins, String strPluginTypeFilter )
308     {
309         Collection<Plugin> list = new ArrayList<>( );
310         if ( strPluginTypeFilter == null )
311         {
312             return listPlugins;
313         }
314 
315         for ( Plugin plugin : listPlugins )
316         {
317             boolean filter = false;
318             if ( strPluginTypeFilter.equals( PARAM_PLUGIN_TYPE_APPLICATION ) )
319             {
320                 // skip this plugin
321                 filter = ( plugin.getType( ) & Plugin.PLUGIN_TYPE_APPLICATION ) == 0;
322             }
323             else
324                 if ( strPluginTypeFilter.equals( PARAM_PLUGIN_TYPE_PORTLET ) )
325                 {
326                     // skip this plugin
327                     filter = ( plugin.getType( ) & Plugin.PLUGIN_TYPE_PORTLET ) == 0;
328                 }
329                 else
330                     if ( strPluginTypeFilter.equals( PARAM_PLUGIN_TYPE_FEATURE ) )
331                     {
332                         // skip this plugin
333                         filter = ( plugin.getType( ) & Plugin.PLUGIN_TYPE_FEATURE ) == 0;
334                     }
335                     else
336                         if ( strPluginTypeFilter.equals( PARAM_PLUGIN_TYPE_INSERTSERVICE ) )
337                         {
338                             // skip this plugin
339                             filter = ( plugin.getType( ) & Plugin.PLUGIN_TYPE_INSERTSERVICE ) == 0;
340                         }
341                         else
342                             if ( strPluginTypeFilter.equals( PARAM_PLUGIN_TYPE_CONTENTSERVICE ) )
343                             {
344                                 // skip this plugin
345                                 filter = ( plugin.getType( ) & Plugin.PLUGIN_TYPE_CONTENTSERVICE ) == 0;
346                             }
347 
348             if ( !filter )
349             {
350                 list.add( plugin );
351             }
352         }
353         return list;
354     }
355 
356     /**
357      * Create a ReferenceList containing all Plugin types
358      * 
359      * @param locale
360      *            The Locale
361      * @return A ReferenceList containing all Plugin types
362      */
363     private ReferenceList getPluginTypeFilterList( Locale locale )
364     {
365         ReferenceListnceList.html#ReferenceList">ReferenceList list = new ReferenceList( );
366         list.addItem( PARAM_PLUGIN_TYPE_ALL, I18nService.getLocalizedString( PROPERTY_PLUGIN_TYPE_NAME_ALL, locale ) );
367         list.addItem( PARAM_PLUGIN_TYPE_APPLICATION, I18nService.getLocalizedString( PROPERTY_PLUGIN_TYPE_NAME_APPLICATION, locale ) );
368         list.addItem( PARAM_PLUGIN_TYPE_PORTLET, I18nService.getLocalizedString( PROPERTY_PLUGIN_TYPE_NAME_PORTLET, locale ) );
369         list.addItem( PARAM_PLUGIN_TYPE_FEATURE, I18nService.getLocalizedString( PROPERTY_PLUGIN_TYPE_NAME_FEATURE, locale ) );
370         list.addItem( PARAM_PLUGIN_TYPE_INSERTSERVICE, I18nService.getLocalizedString( PROPERTY_PLUGIN_TYPE_NAME_INSERTSERVICE, locale ) );
371         list.addItem( PARAM_PLUGIN_TYPE_CONTENTSERVICE, I18nService.getLocalizedString( PROPERTY_PLUGIN_TYPE_NAME_CONTENTSERVICE, locale ) );
372 
373         return list;
374     }
375 
376     /**
377      * Return a list of pools
378      * 
379      * @return listPools the list of pools
380      */
381     private ReferenceList getPoolsList( )
382     {
383         ReferenceListst.html#ReferenceList">ReferenceList listPools = new ReferenceList( );
384         listPools.addItem( AppConnectionService.NO_POOL_DEFINED, " " );
385         AppConnectionService.getPoolList( listPools );
386 
387         return listPools;
388     }
389 
390     /**
391      * Returns the status of the existence of a portlet on the site
392      *
393      * @param strPluginHomeClass
394      *            The home class of the plugin
395      * @return The existence status as a boolean
396      */
397     private boolean isPortletExists( String strPluginHomeClass )
398     {
399         String strPortletTypeId = PortletTypeHome.getPortletTypeId( strPluginHomeClass );
400 
401         return ( PortletTypeHome.getNbPortletTypeByPortlet( strPortletTypeId ) != 0 );
402     }
403 
404     /**
405      * Verify the core compatibility for a plugin
406      *
407      * @param plugin
408      *            The plugin
409      * @return true if compatible with the current core version
410      */
411     private boolean verifyCoreCompatibility( Plugin plugin )
412     {
413         String strCoreVersion = AppInfo.getVersion( );
414 
415         // Remove version qualifier (-SNAPSHOT, -RC-XX, ...)
416         int nPos = strCoreVersion.indexOf( '-' );
417 
418         if ( nPos > 0 )
419         {
420             strCoreVersion = strCoreVersion.substring( 0, nPos );
421         }
422 
423         String [ ] coreVersion = strCoreVersion.split( "\\." );
424 
425         String strMinCoreVersion = ( plugin.getMinCoreVersion( ) == null ) ? "" : plugin.getMinCoreVersion( );
426         String strMaxCoreVersion = ( plugin.getMaxCoreVersion( ) == null ) ? "" : plugin.getMaxCoreVersion( );
427 
428         // test the min core version
429         boolean bMin = ( strMinCoreVersion == null ) || strMinCoreVersion.trim( ).equals( "" );
430 
431         if ( ( strMinCoreVersion != null ) && !strMinCoreVersion.trim( ).equals( "" ) )
432         {
433             String [ ] minCoreVersion = strMinCoreVersion.split( "\\." );
434 
435             if ( checkCoreMinCompatibility( minCoreVersion, coreVersion ) )
436             {
437                 AppLogService.debug( "Min core version ok : {}", plugin.getMinCoreVersion( ) );
438                 bMin = true;
439             }
440         }
441 
442         // test the max core version
443         boolean bMax = ( strMaxCoreVersion == null ) || strMaxCoreVersion.trim( ).equals( "" );
444 
445         if ( ( strMaxCoreVersion != null ) && !strMaxCoreVersion.trim( ).equals( "" ) )
446         {
447             String [ ] maxCoreVersion = strMaxCoreVersion.split( "\\." );
448 
449             if ( checkCoreMaxCompatibility( maxCoreVersion, coreVersion ) )
450             {
451                 AppLogService.debug( "Max core version ok : {}", plugin.getMaxCoreVersion( ) );
452                 bMax = true;
453             }
454         }
455 
456         return bMin && bMax;
457     }
458 
459     /**
460      * Checks the compatibility
461      * 
462      * @param minCoreVersion
463      *            The min core version
464      * @param coreVersion
465      *            The current core version
466      * @return true if compatible with the current core version
467      */
468     private boolean checkCoreMinCompatibility( String [ ] minCoreVersion, String [ ] coreVersion )
469     {
470         for ( int i = 0; i < Math.min( minCoreVersion.length, coreVersion.length ); ++i )
471         {
472             if ( ( Integer.parseInt( minCoreVersion [i] ) ) < ( Integer.parseInt( coreVersion [i] ) ) )
473             {
474                 return true;
475             }
476 
477             if ( ( Integer.parseInt( minCoreVersion [i] ) ) > ( Integer.parseInt( coreVersion [i] ) ) )
478             {
479                 return false;
480             }
481         }
482 
483         return true; // inclusive
484     }
485 
486     /**
487      * Checks the compatibility
488      * 
489      * @param maxCoreVersion
490      *            The max core version
491      * @param coreVersion
492      *            The current core version
493      * @return true if compatible with the current core version
494      */
495     private boolean checkCoreMaxCompatibility( String [ ] maxCoreVersion, String [ ] coreVersion )
496     {
497         for ( int i = 0; i < Math.min( maxCoreVersion.length, coreVersion.length ); ++i )
498         {
499             if ( ( Integer.parseInt( maxCoreVersion [i] ) ) > ( Integer.parseInt( coreVersion [i] ) ) )
500             {
501                 return true;
502             }
503 
504             if ( ( Integer.parseInt( maxCoreVersion [i] ) ) < ( Integer.parseInt( coreVersion [i] ) ) )
505             {
506                 return false;
507             }
508         }
509 
510         return false; // exclusive
511     }
512 }