PluginService.java

  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.service.plugin;

  35. import java.io.File;
  36. import java.io.FileInputStream;
  37. import java.io.FilenameFilter;
  38. import java.util.ArrayList;
  39. import java.util.Collection;
  40. import java.util.HashMap;
  41. import java.util.List;
  42. import java.util.Map;
  43. import java.util.Properties;
  44. import java.util.TreeSet;

  45. import fr.paris.lutece.portal.service.database.AppConnectionService;
  46. import fr.paris.lutece.portal.service.datastore.DatastoreService;
  47. import fr.paris.lutece.portal.service.init.LuteceInitException;
  48. import fr.paris.lutece.portal.service.util.AppLogService;
  49. import fr.paris.lutece.portal.service.util.AppPathService;
  50. import fr.paris.lutece.util.filesystem.FileListFilter;

  51. /**
  52.  * This class provides services and utilities for plugins management
  53.  */
  54. public final class PluginService
  55. {
  56.     // Constantes
  57.     private static final String PATH_CONF = "path.conf";
  58.     private static final String CORE_XML = "core.xml";
  59.     private static final String CORE = "core";
  60.     private static Plugin _pluginCore;
  61.     private static final String PATH_PLUGIN = "path.plugins";
  62.     private static final String FILE_PLUGINS_STATUS = "plugins.dat";
  63.     private static final String EXTENSION_FILE = "xml";
  64.     private static final String PROPERTY_IS_INSTALLED = ".installed";
  65.     private static final String PROPERTY_DB_POOL_NAME = ".pool";
  66.     private static final String KEY_PLUGINS_STATUS = "core.plugins.status.";

  67.     // Variables
  68.     private static Map<String, Plugin> _mapPlugins = new HashMap<>( );
  69.     private static List<PluginEventListener> _listPluginEventListeners = new ArrayList<>( );

  70.     /**
  71.      * Creates a new PluginService object.
  72.      */
  73.     private PluginService( )
  74.     {
  75.     }

  76.     /**
  77.      * Initialize the service
  78.      *
  79.      * @throws LuteceInitException
  80.      *             If an error occured
  81.      */
  82.     public static void init( ) throws LuteceInitException
  83.     {
  84.         loadPluginsStatus( );
  85.         _mapPlugins.clear( );
  86.         loadCoreComponents( );
  87.         loadPlugins( );
  88.     }

  89.     /**
  90.      * Returns the plugins file list
  91.      *
  92.      * @return the plugins file list as a File[]
  93.      */
  94.     public static Collection<Plugin> getPluginList( )
  95.     {
  96.         return new TreeSet<>( _mapPlugins.values( ) );
  97.     }

  98.     /**
  99.      * Returns a Plugin object from its name
  100.      *
  101.      * @param strPluginName
  102.      *            The name of the plugin
  103.      * @return The Plugin object corresponding to the name
  104.      */
  105.     public static Plugin getPlugin( String strPluginName )
  106.     {
  107.         return _mapPlugins.get( strPluginName );
  108.     }

  109.     /**
  110.      * Load components of the core.
  111.      *
  112.      * @throws LuteceInitException
  113.      *             If an error occured
  114.      */
  115.     private static void loadCoreComponents( ) throws LuteceInitException
  116.     {
  117.         File file = new File( AppPathService.getPath( PATH_CONF, CORE_XML ) );

  118.         if ( file.exists( ) )
  119.         {
  120.             loadPluginFromFile( file, false );
  121.         }
  122.     }

  123.     /**
  124.      * Load all plugins installed on the system.
  125.      *
  126.      * @throws LuteceInitException
  127.      *             If an error occured
  128.      */
  129.     private static void loadPlugins( ) throws LuteceInitException
  130.     {
  131.         File dirPlugin = new File( AppPathService.getPath( PATH_PLUGIN ) );

  132.         if ( dirPlugin.exists( ) )
  133.         {
  134.             FilenameFilter select = new FileListFilter( "", EXTENSION_FILE );
  135.             File [ ] listFile = dirPlugin.listFiles( select );

  136.             for ( File file : listFile )
  137.             {
  138.                 loadPluginFromFile( file, true );
  139.             }
  140.         }
  141.     }

  142.     /**
  143.      * Load a plugin from a file definition
  144.      *
  145.      * @param file
  146.      *            The plugin file
  147.      * @param bRegisterAsPlugin
  148.      *            Register it as a plugin : true for plugins, false for core components file
  149.      * @throws LuteceInitException
  150.      *             If an error occured
  151.      */
  152.     private static void loadPluginFromFile( File file, boolean bRegisterAsPlugin ) throws LuteceInitException
  153.     {
  154.         PluginFile pluginFile = new PluginFile( );
  155.         pluginFile.load( file.getAbsolutePath( ) );

  156.         String strPluginClass = pluginFile.getPluginClass( );

  157.         if ( strPluginClass != null )
  158.         {
  159.             try
  160.             {
  161.                 Plugin plugin = (Plugin) Class.forName( strPluginClass ).newInstance( );
  162.                 plugin.load( pluginFile );

  163.                 if ( bRegisterAsPlugin )
  164.                 {
  165.                     plugin.setStatus( getPluginStatus( plugin ) );
  166.                     registerPlugin( plugin );
  167.                 }
  168.                 else
  169.                 {
  170.                     plugin.setStatus( true );
  171.                     registerCore( plugin );
  172.                 }

  173.                 // If the plugin requires a database connection pool then
  174.                 // get the pool name and initialize its ConnectionService
  175.                 if ( plugin.isDbPoolRequired( ) )
  176.                 {
  177.                     String strPoolName = getPluginPoolName( plugin );
  178.                     plugin.setPoolName( strPoolName );
  179.                     plugin.initConnectionService( strPoolName );
  180.                 }

  181.                 plugin.init( );

  182.                 // plugin installed event
  183.                 PluginEvent event = new PluginEvent( plugin, PluginEvent.PLUGIN_INSTALLED );
  184.                 notifyListeners( event );
  185.             }
  186.             catch( Exception e )
  187.             {
  188.                 throw new LuteceInitException( "Error instantiating plugin defined in file : " + file.getAbsolutePath( ), e );
  189.             }
  190.         }
  191.         else
  192.         {
  193.             AppLogService.error( "No plugin class defined in file : {}", file.getAbsolutePath( ) );
  194.         }
  195.     }

  196.     /**
  197.      * Register the plugin as a plugin loaded in the system
  198.      *
  199.      * @param plugin
  200.      *            The plugin object
  201.      */
  202.     private static void registerPlugin( Plugin plugin )
  203.     {
  204.         _mapPlugins.put( plugin.getName( ), plugin );

  205.         String strStatusWarning = ( plugin.isInstalled( ) ) ? "" : " *** Warning : current status is 'disabled' ***";
  206.         AppLogService.info( "New Plugin registered : {} {}", plugin.getName( ), strStatusWarning );
  207.     }

  208.     /**
  209.      * Gets the core plugin
  210.      *
  211.      * @param plugin
  212.      *            the plugin
  213.      */
  214.     private static synchronized void registerCore( Plugin plugin )
  215.     {
  216.         _pluginCore = plugin;
  217.     }

  218.     /**
  219.      * Gets the core.
  220.      *
  221.      * @return the core
  222.      */
  223.     public static Plugin getCore( )
  224.     {
  225.         return _pluginCore;
  226.     }

  227.     /**
  228.      * Update plugins data.
  229.      *
  230.      * @param plugin
  231.      *            The plugin object
  232.      */
  233.     public static void updatePluginData( Plugin plugin )
  234.     {
  235.         String strKey = getInstalledKey( plugin.getName( ) );
  236.         String strValue = plugin.isInstalled( ) ? DatastoreService.VALUE_TRUE : DatastoreService.VALUE_FALSE;
  237.         DatastoreService.setInstanceDataValue( strKey, strValue );

  238.         if ( plugin.isDbPoolRequired( ) )
  239.         {
  240.             DatastoreService.setInstanceDataValue( getPoolNameKey( plugin.getName( ) ), plugin.getDbPoolName( ) );
  241.         }
  242.     }

  243.     /**
  244.      * Build the datastore key for a given plugin
  245.      *
  246.      * @param strPluginName
  247.      *            The plugin name
  248.      * @return The key
  249.      */
  250.     private static String getInstalledKey( String strPluginName )
  251.     {
  252.         return KEY_PLUGINS_STATUS + strPluginName + PROPERTY_IS_INSTALLED;
  253.     }

  254.     /**
  255.      * Build the datastore key for a given plugin
  256.      *
  257.      * @param strPluginName
  258.      *            The plugin name
  259.      * @return The key
  260.      */
  261.     private static String getPoolNameKey( String strPluginName )
  262.     {
  263.         return KEY_PLUGINS_STATUS + strPluginName + PROPERTY_DB_POOL_NAME;
  264.     }

  265.     /**
  266.      * Load plugins status
  267.      */
  268.     private static void loadPluginsStatus( )
  269.     {
  270.         // Load default values from the plugins.dat file
  271.         String strPluginStatusFile = AppPathService.getPath( PATH_PLUGIN, FILE_PLUGINS_STATUS );
  272.         File file = new File( strPluginStatusFile );
  273.         Properties props = new Properties( );

  274.         try ( FileInputStream fis = new FileInputStream( file ) )
  275.         {
  276.             props.load( fis );
  277.         }
  278.         catch( Exception e )
  279.         {
  280.             AppLogService.error( "Error loading plugin defined in file : {}", file.getAbsolutePath( ), e );
  281.         }

  282.         // If the keys aren't found in the datastore then create a key in it
  283.         for ( String strKey : props.stringPropertyNames( ) )
  284.         {
  285.             // Initialize plugins status into Datastore
  286.             int nPos = strKey.indexOf( PROPERTY_IS_INSTALLED );

  287.             if ( nPos > 0 )
  288.             {
  289.                 String strPluginName = strKey.substring( 0, nPos );
  290.                 String strDSKey = getInstalledKey( strPluginName );

  291.                 if ( !DatastoreService.existsInstanceKey( strDSKey ) )
  292.                 {
  293.                     String strValue = String.valueOf( props.getProperty( strKey ).equals( "1" ) );
  294.                     DatastoreService.setInstanceDataValue( strDSKey, strValue );
  295.                 }
  296.             }

  297.             // Initialize plugins connection pool into Datastore
  298.             nPos = strKey.indexOf( PROPERTY_DB_POOL_NAME );

  299.             if ( nPos > 0 )
  300.             {
  301.                 String strPluginName = strKey.substring( 0, nPos );
  302.                 String strDSKey = getPoolNameKey( strPluginName );

  303.                 if ( !DatastoreService.existsInstanceKey( strDSKey ) )
  304.                 {
  305.                     String strValue = props.getProperty( strKey );
  306.                     DatastoreService.setInstanceDataValue( strDSKey, strValue );
  307.                 }
  308.             }
  309.         }
  310.     }

  311.     /**
  312.      * Gets the plugin status
  313.      *
  314.      * @param plugin
  315.      *            The plugin object
  316.      * @return true if installed, otherwise false
  317.      */
  318.     private static boolean getPluginStatus( Plugin plugin )
  319.     {
  320.         String strValue = DatastoreService.getInstanceDataValue( getInstalledKey( plugin.getName( ) ), DatastoreService.VALUE_FALSE );

  321.         return strValue.equals( DatastoreService.VALUE_TRUE );
  322.     }

  323.     /**
  324.      * Gets the pool that should be used by the plugin
  325.      *
  326.      * @param plugin
  327.      *            The plugin Object
  328.      * @return the pool name
  329.      */
  330.     private static String getPluginPoolName( Plugin plugin )
  331.     {
  332.         String strPoolname = DatastoreService.getInstanceDataValue( getPoolNameKey( plugin.getName( ) ), AppConnectionService.NO_POOL_DEFINED );

  333.         if ( strPoolname.equals( AppConnectionService.NO_POOL_DEFINED ) && plugin.isDbPoolRequired( ) && !plugin.getName( ).equals( CORE ) )
  334.         {
  335.             AppLogService.info( " *** WARNING *** - The plugin '{}' has no pool defined in db.properties or datastore. Using the default pool '{}' instead.",
  336.                     plugin, AppConnectionService.DEFAULT_POOL_NAME );
  337.             strPoolname = AppConnectionService.DEFAULT_POOL_NAME;
  338.         }

  339.         return strPoolname;
  340.     }

  341.     /**
  342.      * Gets the plugin status enable / disable
  343.      *
  344.      * @param strPluginName
  345.      *            The plugin name
  346.      * @return True if the plugin is enable, otherwise false
  347.      */
  348.     public static boolean isPluginEnable( String strPluginName )
  349.     {
  350.         if ( strPluginName.equals( CORE ) )
  351.         {
  352.             return true;
  353.         }

  354.         Plugin plugin = getPlugin( strPluginName );

  355.         return ( ( plugin != null ) && ( plugin.isInstalled( ) ) );
  356.     }

  357.     /**
  358.      * Register a Plugin Event Listener
  359.      *
  360.      * @param listener
  361.      *            The listener
  362.      */
  363.     public static void registerPluginEventListener( PluginEventListener listener )
  364.     {
  365.         _listPluginEventListeners.add( listener );
  366.     }

  367.     /**
  368.      * Notify an event to all Plugin Event Listeners
  369.      *
  370.      * @param event
  371.      *            The event
  372.      */
  373.     public static void notifyListeners( PluginEvent event )
  374.     {
  375.         for ( PluginEventListener listener : _listPluginEventListeners )
  376.         {
  377.             listener.processPluginEvent( event );
  378.         }
  379.     }
  380. }