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