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.service.plugin;
35  
36  import fr.paris.lutece.portal.service.database.AppConnectionService;
37  import fr.paris.lutece.portal.service.datastore.DatastoreService;
38  import fr.paris.lutece.portal.service.init.LuteceInitException;
39  import fr.paris.lutece.portal.service.util.AppLogService;
40  import fr.paris.lutece.portal.service.util.AppPathService;
41  import fr.paris.lutece.util.filesystem.FileListFilter;
42  
43  import java.io.File;
44  import java.io.FileInputStream;
45  import java.io.FilenameFilter;
46  import java.io.IOException;
47  
48  import java.util.ArrayList;
49  import java.util.Collection;
50  import java.util.HashMap;
51  import java.util.List;
52  import java.util.Map;
53  import java.util.Properties;
54  import java.util.TreeSet;
55  
56  
57  /**
58   * This class provides services and utilities for plugins management
59   */
60  public final class PluginService
61  {
62      // Constantes
63      private static final String PATH_CONF = "path.conf";
64      private static final String CORE_XML = "core.xml";
65      private static final String CORE = "core";
66      private static Plugin _pluginCore;
67      private static final String PATH_PLUGIN = "path.plugins";
68      private static final String FILE_PLUGINS_STATUS = "plugins.dat";
69      private static final String EXTENSION_FILE = "xml";
70      private static final String PROPERTY_IS_INSTALLED = ".installed";
71      private static final String PROPERTY_DB_POOL_NAME = ".pool";
72      private static final String KEY_PLUGINS_STATUS = "core.plugins.status.";
73  
74      // Variables
75      private static Map<String, Plugin> _mapPlugins = new HashMap<String, Plugin>(  );
76      private static List<PluginEventListener> _listPluginEventListeners = new ArrayList<PluginEventListener>(  );
77  
78      /**
79       * Creates a new PluginService object.
80       */
81      private PluginService(  )
82      {
83      }
84  
85      /**
86       * Initialize the service
87       * @throws LuteceInitException If an error occured
88       */
89      public static void init(  ) throws LuteceInitException
90      {
91          loadPluginsStatus(  );
92          _mapPlugins.clear(  );
93          loadCoreComponents(  );
94          loadPlugins(  );
95      }
96  
97      /**
98       * Returns the plugins file list
99       *
100      * @return the plugins file list as a File[]
101      */
102     public static Collection<Plugin> getPluginList(  )
103     {
104         TreeSet<Plugin> setSorted = new TreeSet<Plugin>( _mapPlugins.values(  ) );
105 
106         return setSorted;
107     }
108 
109     /**
110      * Returns a Plugin object from its name
111      *
112      * @param strPluginName The name of the plugin
113      * @return The Plugin object corresponding to the name
114      */
115     public static Plugin getPlugin( String strPluginName )
116     {
117         return _mapPlugins.get( strPluginName );
118     }
119 
120     /**
121      * Load components of the core.
122      * @throws LuteceInitException If an error occured
123      */
124     private static void loadCoreComponents(  ) throws LuteceInitException
125     {
126         File file = new File( AppPathService.getPath( PATH_CONF, CORE_XML ) );
127 
128         if ( file.exists(  ) )
129         {
130             loadPluginFromFile( file, false );
131         }
132     }
133 
134     /**
135      * Load all plugins installed on the system.
136      * @throws LuteceInitException If an error occured
137      */
138     private static void loadPlugins(  ) throws LuteceInitException
139     {
140         File dirPlugin = new File( AppPathService.getPath( PATH_PLUGIN ) );
141 
142         if ( dirPlugin.exists(  ) )
143         {
144             FilenameFilter select = new FileListFilter( "", EXTENSION_FILE );
145             File[] listFile = dirPlugin.listFiles( select );
146 
147             for ( File file : listFile )
148             {
149                 loadPluginFromFile( file, true );
150             }
151         }
152     }
153 
154     /**
155      * Load a plugin from a file definition
156      * @param file The plugin file
157      * @param bRegisterAsPlugin Register it as a plugin : true for plugins,
158      *            false for core components file
159      * @throws LuteceInitException If an error occured
160      */
161     private static void loadPluginFromFile( File file, boolean bRegisterAsPlugin )
162         throws LuteceInitException
163     {
164         PluginFile pluginFile = new PluginFile(  );
165         pluginFile.load( file.getAbsolutePath(  ) );
166 
167         String strPluginClass = pluginFile.getPluginClass(  );
168 
169         if ( strPluginClass != null )
170         {
171             try
172             {
173                 Plugin plugin = (Plugin) Class.forName( strPluginClass ).newInstance(  );
174                 plugin.load( pluginFile );
175 
176                 if ( bRegisterAsPlugin )
177                 {
178                     plugin.setStatus( getPluginStatus( plugin ) );
179                     registerPlugin( plugin );
180                 }
181                 else
182                 {
183                     plugin.setStatus( true );
184                     registerCore( plugin );
185                 }
186 
187                 // If the plugin requires a database connection pool then
188                 // get the pool name and initialize its ConnectionService
189                 if ( plugin.isDbPoolRequired(  ) )
190                 {
191                     String strPoolName = getPluginPoolName( plugin );
192                     plugin.setPoolName( strPoolName );
193                     plugin.initConnectionService( strPoolName );
194                 }
195 
196                 plugin.init(  );
197 
198                 // plugin installed event
199                 PluginEvent event = new PluginEvent( plugin, PluginEvent.PLUGIN_INSTALLED );
200                 notifyListeners( event );
201             }
202             catch ( Exception e )
203             {
204                 throw new LuteceInitException( "Error instantiating plugin defined in file : " +
205                     file.getAbsolutePath(  ), e );
206             }
207         }
208         else
209         {
210             AppLogService.error( "No plugin class defined in file : " + file.getAbsolutePath(  ) );
211         }
212     }
213 
214     /**
215      * Register the plugin as a plugin loaded in the system
216      *
217      * @param plugin The plugin object
218      */
219     private static void registerPlugin( Plugin plugin )
220     {
221         _mapPlugins.put( plugin.getName(  ), plugin );
222 
223         String strStatusWarning = ( plugin.isInstalled(  ) ) ? "" : " *** Warning : current status is 'disabled' ***";
224         AppLogService.info( "New Plugin registered : " + plugin.getName(  ) + strStatusWarning );
225     }
226 
227     /**
228      * Gets the core plugin
229      * @param plugin the plugin
230      */
231     private static synchronized void registerCore( Plugin plugin )
232     {
233         _pluginCore = plugin;
234     }
235 
236     /**
237      * Gets the core.
238      *
239      * @return the core
240      */
241     public static Plugin getCore(  )
242     {
243         return _pluginCore;
244     }
245 
246     /**
247      * Update plugins data.
248      *
249      * @param plugin The plugin object
250      */
251     public static void updatePluginData( Plugin plugin )
252     {
253         String strKey = getInstalledKey( plugin.getName(  ) );
254         String strValue = plugin.isInstalled(  ) ? DatastoreService.VALUE_TRUE : DatastoreService.VALUE_FALSE;
255         DatastoreService.setInstanceDataValue( strKey, strValue );
256 
257         if ( plugin.isDbPoolRequired(  ) )
258         {
259             DatastoreService.setInstanceDataValue( getPoolNameKey( plugin.getName(  ) ), plugin.getDbPoolName(  ) );
260         }
261     }
262 
263     /**
264      * Build the datastore key for a given plugin
265      * @param strPluginName The plugin name
266      * @return The key
267      */
268     private static String getInstalledKey( String strPluginName )
269     {
270         return KEY_PLUGINS_STATUS + strPluginName + PROPERTY_IS_INSTALLED;
271     }
272 
273     /**
274      * Build the datastore key for a given plugin
275      * @param strPluginName The plugin name
276      * @return The key
277      */
278     private static String getPoolNameKey( String strPluginName )
279     {
280         return KEY_PLUGINS_STATUS + strPluginName + PROPERTY_DB_POOL_NAME;
281     }
282 
283     /**
284      * Load plugins status
285      */
286     private static void loadPluginsStatus(  )
287     {
288         // Load default values from the plugins.dat file
289         String strPluginStatusFile = AppPathService.getPath( PATH_PLUGIN, FILE_PLUGINS_STATUS );
290         File file = new File( strPluginStatusFile );
291         Properties props = new Properties(  );
292         FileInputStream fis = null;
293 
294         try
295         {
296             fis = new FileInputStream( file );
297             props.load( fis );
298         }
299         catch ( Exception e )
300         {
301             AppLogService.error( "Error loading plugin defined in file : " + file.getAbsolutePath(  ), e );
302         }
303         finally
304         {
305             if ( fis != null )
306             {
307                 try
308                 {
309                     fis.close(  );
310                 }
311                 catch ( IOException e )
312                 {
313                     AppLogService.error( e.getMessage(  ), e );
314                 }
315             }
316         }
317 
318         // If the keys aren't found in the datastore then create a key in it
319         for ( String strKey : props.stringPropertyNames(  ) )
320         {
321             // Initialize plugins status into Datastore
322             int nPos = strKey.indexOf( PROPERTY_IS_INSTALLED );
323 
324             if ( nPos > 0 )
325             {
326                 String strPluginName = strKey.substring( 0, nPos );
327                 String strDSKey = getInstalledKey( strPluginName );
328 
329                 if ( !DatastoreService.existsInstanceKey( strDSKey ) )
330                 {
331                     String strValue = props.getProperty( strKey ).equals( "1" ) ? DatastoreService.VALUE_TRUE
332                                                                                 : DatastoreService.VALUE_FALSE;
333                     DatastoreService.setInstanceDataValue( strDSKey, strValue );
334                 }
335             }
336 
337             // Initialize plugins connection pool into Datastore
338             nPos = strKey.indexOf( PROPERTY_DB_POOL_NAME );
339 
340             if ( nPos > 0 )
341             {
342                 String strPluginName = strKey.substring( 0, nPos );
343                 String strDSKey = getPoolNameKey( strPluginName );
344 
345                 if ( !DatastoreService.existsInstanceKey( strDSKey ) )
346                 {
347                     String strValue = props.getProperty( strKey );
348                     DatastoreService.setInstanceDataValue( strDSKey, strValue );
349                 }
350             }
351         }
352     }
353 
354     /**
355      * Gets the plugin status
356      *
357      * @param plugin The plugin object
358      * @return true if installed, otherwise false
359      */
360     private static boolean getPluginStatus( Plugin plugin )
361     {
362         String strValue = DatastoreService.getInstanceDataValue( getInstalledKey( plugin.getName(  ) ),
363                 DatastoreService.VALUE_FALSE );
364 
365         return strValue.equals( DatastoreService.VALUE_TRUE );
366     }
367 
368     /**
369      * Gets the pool that should be used by the plugin
370      *
371      * @param plugin The plugin Object
372      * @return the pool name
373      */
374     private static String getPluginPoolName( Plugin plugin )
375     {
376         String strPoolname = DatastoreService.getInstanceDataValue( getPoolNameKey( plugin.getName(  ) ),
377                 AppConnectionService.NO_POOL_DEFINED );
378 
379         if ( strPoolname.equals( AppConnectionService.NO_POOL_DEFINED ) && plugin.isDbPoolRequired(  ) &&
380                 !plugin.getName(  ).equals( CORE ) )
381         {
382             AppLogService.info( " *** WARNING *** - The plugin '" + plugin +
383                 "' has no pool defined in db.properties or datastore. Using the default pool '" +
384                 AppConnectionService.DEFAULT_POOL_NAME + "' instead." );
385             strPoolname = AppConnectionService.DEFAULT_POOL_NAME;
386         }
387 
388         return strPoolname;
389     }
390 
391     /**
392      * Gets the plugin status enable / disable
393      * @param strPluginName The plugin name
394      * @return True if the plugin is enable, otherwise false
395      */
396     public static boolean isPluginEnable( String strPluginName )
397     {
398         if ( strPluginName.equals( CORE ) )
399         {
400             return true;
401         }
402 
403         Plugin plugin = getPlugin( strPluginName );
404 
405         return ( ( plugin != null ) && ( plugin.isInstalled(  ) ) );
406     }
407 
408     /**
409      * Register a Plugin Event Listener
410      * @param listener The listener
411      */
412     public static void registerPluginEventListener( PluginEventListener listener )
413     {
414         _listPluginEventListeners.add( listener );
415     }
416 
417     /**
418      * Notify an event to all Plugin Event Listeners
419      * @param event The event
420      */
421     public static void notifyListeners( PluginEvent event )
422     {
423         for ( PluginEventListener listener : _listPluginEventListeners )
424         {
425             listener.processPluginEvent( event );
426         }
427     }
428 }