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.plugins.updater.service;
35  
36  import fr.paris.lutece.plugins.updater.business.resource.DirectoryTreeResource;
37  import fr.paris.lutece.plugins.updater.business.resource.FilePatternResource;
38  import fr.paris.lutece.plugins.updater.business.resource.FileResource;
39  import fr.paris.lutece.plugins.updater.business.resource.FileSystemResource;
40  import fr.paris.lutece.plugins.updater.util.sql.SqlUtils;
41  import fr.paris.lutece.util.filesystem.FileNameComparator;
42  
43  import org.apache.commons.io.FileUtils;
44  
45  import org.apache.log4j.Logger;
46  
47  import java.io.File;
48  import java.io.IOException;
49  
50  import java.sql.SQLException;
51  
52  import java.util.ArrayList;
53  import java.util.Arrays;
54  import java.util.List;
55  import java.util.TreeSet;
56  
57  
58  /**
59   * Plugin Manager Service
60   */
61  public final class PluginManagerService
62  {
63      private static final int FILE = 1;
64      private static final int DIRECTORY_TREE = 2;
65      private static final int FILE_PREFIX = 3;
66      private static final String PATH_BACKUP = "/plugins/updater/backup/";
67      private static final String PATH_DEPLOY = "/plugins/updater/deploy/";
68      private static final String FOLDER_WEBAPP = "/webapp";
69      private static final String FOLDER_SQL = "/sql";
70      private static PluginManagerService _singleton = new PluginManagerService(  );
71      private static String _strWebAppPath;
72      private static Logger _logger = Logger.getLogger( "lutece.plugins.updater" );
73  
74      /**
75       * Private constructor
76       */
77      private PluginManagerService(  )
78      {
79      }
80  
81      /**
82       * Returns the unique instance of the service
83       * @return The unique instance of the service
84       */
85      public static PluginManagerService getInstance(  )
86      {
87          return _singleton;
88      }
89  
90      /**
91       * Returns the WebAppPath
92       *
93       * @return The WebAppPath
94       */
95      public String getWebAppPath(  )
96      {
97          // For unit testing _strWebAppPath can be initialized manualy without 
98          // using AppPathService.getWebAppPath()
99          return _strWebAppPath;
100     }
101 
102     /**
103      * Sets the WebAppPath
104      *
105      * @param strWebAppPath The WebAppPath
106      */
107     public void setWebAppPath( String strWebAppPath )
108     {
109         _strWebAppPath = strWebAppPath;
110     }
111 
112     /**
113      * Clean plugins before update
114      * @param nDeleteMode The deletion mode
115      */
116     public void cleanPluginsMarkedForUpdate( int nDeleteMode )
117     {
118         _logger.info( "Searching for plugin to remove before update ..." );
119 
120         String strDeployDirectory = getWebAppPath(  ) + PATH_DEPLOY;
121         File fDeployDirectory = new File( strDeployDirectory );
122         boolean bFound = false;
123 
124         if ( fDeployDirectory.exists(  ) )
125         {
126             File[] files = fDeployDirectory.listFiles(  );
127 
128             if ( files.length > 0 )
129             {
130                 for ( int i = 0; i < files.length; i++ )
131                 {
132                     File fPluginDirectory = files[i];
133                     String strPluginName = fPluginDirectory.getName(  );
134                     _logger.info( "An update has been found for plugin : " + strPluginName +
135                         ". The installed plugin will be deleted" );
136                     bFound = true;
137                     cleanPlugin( strPluginName, nDeleteMode, true );
138                 }
139             }
140         }
141 
142         if ( !bFound )
143         {
144             _logger.info( "No plugin to removed was found." );
145         }
146     }
147 
148     /**
149      * Install new plugins or updates (deploy => webapp)
150      */
151     public void installPlugins(  )
152     {
153         _logger.info( "Searching for plugin to install ..." );
154 
155         String strDeployDirectory = getWebAppPath(  ) + PATH_DEPLOY;
156         File fDeployDirectory = new File( strDeployDirectory );
157         boolean bFound = false;
158 
159         if ( fDeployDirectory.exists(  ) )
160         {
161             File[] files = fDeployDirectory.listFiles(  );
162 
163             if ( files.length > 0 )
164             {
165                 for ( int i = 0; i < files.length; i++ )
166                 {
167                     File fPluginDirectory = files[i];
168                     String strPluginName = fPluginDirectory.getName(  );
169 
170                     try
171                     {
172                         _logger.info( "Installation data has been found for plugin : " + strPluginName );
173                         installPlugin( strPluginName );
174                         bFound = true;
175                         _logger.info( "Plugin '" + strPluginName + "' installed" );
176                     }
177                     catch ( UpdaterInstallException ex )
178                     {
179                         _logger.info( "Plugin '" + strPluginName + "'  installation failed : " + ex.getMessage(  ) );
180                         _logger.info( "Plugin '" + strPluginName + "'  installation rolled back" );
181                         cleanPlugin( strPluginName, 0, false );
182                     }
183                 }
184             }
185         }
186 
187         if ( !bFound )
188         {
189             _logger.info( "No plugin to install or update was found." );
190         }
191     }
192 
193     /**
194      * Clean an installed version of a plugin in the webapp. The process backup
195      * and remove the current version of the plugin.
196      * @param strPluginName The plugin name
197      * @param nDeleteMode The deletion mode
198      * @param bBackup Backup before cleaning
199      */
200     public void cleanPlugin( String strPluginName, int nDeleteMode, boolean bBackup )
201     {
202         if ( bBackup )
203         {
204             // backup the plugin
205             backupPlugin( strPluginName );
206         }
207 
208         try
209         {
210             // remove it from the webapp
211             removePlugin( strPluginName, nDeleteMode );
212         }
213         catch ( Exception e )
214         {
215             restorePlugin( strPluginName );
216         }
217     }
218 
219     /**
220      * Remove a plugin from the webapp
221      * @param strPluginName The plugin name
222      * @param nDeleteMode The deletion mode
223      */
224     public void removePlugin( String strPluginName, int nDeleteMode )
225     {
226         _logger.info( "remove plugin : " + strPluginName );
227 
228         List<FileSystemResource> listResources = getPluginResources( strPluginName );
229 
230         try
231         {
232             for ( FileSystemResource resource : listResources )
233             {
234                 resource.delete( nDeleteMode );
235                 _logger.debug( "resource removed : " + resource.getFullPath(  ) );
236             }
237         }
238         catch ( IOException ex )
239         {
240             _logger.error( "error removing plugin : ", ex );
241         }
242     }
243 
244     /**
245      * Backup the plugin
246      * @param strPluginName The plugin name
247      */
248     public void backupPlugin( String strPluginName )
249     {
250         _logger.info( "backup plugin : " + strPluginName );
251 
252         List<FileSystemResource> listResources = getPluginResources( strPluginName );
253         String strBackupPath = PATH_BACKUP + strPluginName + FOLDER_WEBAPP;
254 
255         try
256         {
257             for ( FileSystemResource resource : listResources )
258             {
259                 _logger.debug( "resource copied : " + resource.getFullPath(  ) );
260                 resource.copy( strBackupPath );
261             }
262         }
263         catch ( IOException ex )
264         {
265             _logger.error( "error backup plugin : ", ex );
266         }
267     }
268 
269     /**
270      * Restore a plugin (backup => webapp)
271      * @param strPluginName The plugin name
272      */
273     public void restorePlugin( String strPluginName )
274     {
275         _logger.info( "restore plugin : " + strPluginName );
276 
277         try
278         {
279             File fileSource = new File( getWebAppPath(  ) + PATH_BACKUP + strPluginName + FOLDER_WEBAPP );
280             File fileDest = new File( getWebAppPath(  ) );
281 
282             FileUtils.copyDirectory( fileSource, fileDest );
283         }
284         catch ( IOException ex )
285         {
286             _logger.error( "error restoring plugin : ", ex );
287         }
288     }
289 
290     /**
291      * Checks if a backup is available for a restore process
292      * @param strPluginName The plugin name
293      * @return true if the plugin can be restored
294      */
295     public boolean checkRestorable( String strPluginName )
296     {
297         String strBackupDirectory = getWebAppPath(  ) + PATH_BACKUP + strPluginName;
298         File fileBackupDirectory = new File( strBackupDirectory );
299 
300         return fileBackupDirectory.exists(  );
301     }
302 
303     /**
304      * Checks if the installation is in progress
305      * @param strPluginName The plugin name
306      * @return true if the installation is in progress
307      */
308     public boolean checkInstallInProgress( String strPluginName )
309     {
310         String strDeployDirectory = getWebAppPath(  ) + PATH_DEPLOY + strPluginName;
311         File fileDeployDirectory = new File( strDeployDirectory );
312 
313         return fileDeployDirectory.exists(  );
314     }
315 
316     /**
317      * Cancel an installation
318      * @param strPluginName The plugin name
319      */
320     public void cancelInstallInProgress( String strPluginName )
321     {
322         // Cancel is performed by removing the plugin directory in the DEPLOY directory
323         String strDeployDirectory = getWebAppPath(  ) + PATH_DEPLOY + strPluginName;
324         File fileDeployDirectory = new File( strDeployDirectory );
325 
326         if ( fileDeployDirectory.exists(  ) )
327         {
328             try
329             {
330                 FileUtils.deleteDirectory( fileDeployDirectory );
331             }
332             catch ( IOException ex )
333             {
334                 _logger.error( "error installing plugin : ", ex );
335             }
336         }
337     }
338 
339     /**
340      * Return the PATH to deploy webapp files of a plugin
341      * @param strPluginName The plugin
342      * @return The path
343      */
344     public String getDeployWebappPath( String strPluginName )
345     {
346         return getWebAppPath(  ) + PATH_DEPLOY + strPluginName + FOLDER_WEBAPP;
347     }
348 
349     /**
350      * Return the PATH to deploy webapp files of a plugin
351      * @param strPluginName The plugin
352      * @return The path
353      */
354     public String getDeploySqlPath( String strPluginName )
355     {
356         return getWebAppPath(  ) + PATH_DEPLOY + strPluginName + FOLDER_SQL;
357     }
358 
359     /**
360      * Runs SQL scripts
361      * @param strScriptsDirectory The scripts directory
362      * @throws UpdaterScriptException If a script exception occurs
363      */
364     private void executeScripts( String strScriptsDirectory )
365         throws UpdaterScriptException
366     {
367         File fileScriptsDirectory = new File( strScriptsDirectory );
368 
369         if ( fileScriptsDirectory.exists(  ) )
370         {
371             try
372             {
373                 // Use a treeset to order files with a comparator
374                 TreeSet<File> set = new TreeSet<File>( new FileNameComparator(  ) );
375 
376                 File[] files = fileScriptsDirectory.listFiles(  );
377 
378                 if ( files != null )
379                 {
380                     set.addAll(Arrays.asList(files));
381 
382                     for ( File file : set )
383                     {
384                         SqlUtils.executeSqlFileScript( file.getAbsolutePath(  ), null );
385                     }
386                 }
387             }
388             catch ( IOException e )
389             {
390                 throw new UpdaterScriptException( "file not found", e );
391             }
392             catch ( SQLException e )
393             {
394                 throw new UpdaterScriptException( "SQL Error", e );
395             }
396         }
397     }
398 
399     /**
400      * Gives all files of a plugin
401      * @param strPluginName The plugin name
402      * @return A list of all file resources owned by a plugin
403      */
404     private List<FileSystemResource> getPluginResources( String strPluginName )
405     {
406         List<FileSystemResource> listResources = new ArrayList<FileSystemResource>(  );
407         addResource( "/css/plugins/" + strPluginName, DIRECTORY_TREE, listResources );
408         addResource( "/js/plugins/" + strPluginName, DIRECTORY_TREE, listResources );
409         addResource( "/images/admin/skin/plugins/" + strPluginName, DIRECTORY_TREE, listResources );
410         addResource( "/images/local/skin/plugins/" + strPluginName, DIRECTORY_TREE, listResources );
411         addResource( "/jsp/site/plugins/" + strPluginName, DIRECTORY_TREE, listResources );
412         addResource( "/jsp/admin/plugins/" + strPluginName, DIRECTORY_TREE, listResources );
413         addResource( "/WEB-INF/classes/fr/paris/lutece/plugins/" + strPluginName + "/resources", DIRECTORY_TREE,
414             listResources );
415         addResource( "/WEB-INF/conf/plugins/" + strPluginName + ".properties", FILE, listResources );
416         addResource( "/WEB-INF/conf/plugins/" + strPluginName + "_context.xml", FILE, listResources );
417         addResource( "/WEB-INF/plugins/" + strPluginName + ".xml", FILE, listResources );
418         //        addResource( "/WEB-INF/plugins/" + strPluginName, DIRECTORY_TREE, listResources );   // Plugin's data
419         addResource( "/WEB-INF/sql/plugins/" + strPluginName, DIRECTORY_TREE, listResources );
420         addResource( "/WEB-INF/templates/admin/plugins/" + strPluginName, DIRECTORY_TREE, listResources );
421         addResource( "/WEB-INF/templates/skin/plugins/" + strPluginName, DIRECTORY_TREE, listResources );
422 
423         addResource( "/WEB-INF/lib/", "plugin-" + strPluginName, FILE_PREFIX, listResources );
424         addResource( "/WEB-INF/xsl/normal/", "portlet_" + strPluginName, FILE_PREFIX, listResources );
425 
426         return listResources;
427     }
428 
429     /**
430      * Add a resource to a list
431      * @param strResource The resource to add
432      * @param nType The resource type
433      * @param listResources The list
434      */
435     private void addResource( String strResource, int nType, List<FileSystemResource> listResources )
436     {
437         addResource( strResource, "", nType, listResources );
438     }
439 
440     /**
441      * Add a resource to a list
442      * @param strResourceDirectory The resource's directory
443      * @param strResourcePattern The resource's pattern
444      * @param nType The resource type
445      * @param listResources The list
446      */
447     private void addResource( String strResourceDirectory, String strResourcePattern, int nType,
448         List<FileSystemResource> listResources )
449     {
450         try
451         {
452             File file = new File( getWebAppPath(  ) + "/" + strResourceDirectory );
453 
454             if ( file.exists(  ) )
455             {
456                 FileSystemResource resource = null;
457 
458                 switch ( nType )
459                 {
460                     case FILE:
461                         resource = new FileResource( getWebAppPath(  ), strResourceDirectory );
462                         _logger.debug( "plugin resource 'file' found : " + file.getPath(  ) );
463 
464                         break;
465 
466                     case DIRECTORY_TREE:
467                         resource = new DirectoryTreeResource( getWebAppPath(  ), strResourceDirectory );
468                         _logger.debug( "plugin resource 'directory tree' found : " + file.getPath(  ) );
469 
470                         break;
471 
472                     case FILE_PREFIX:
473                         resource = new FilePatternResource( getWebAppPath(  ), strResourceDirectory,
474                                 strResourcePattern, FilePatternResource.PATTERN_PREFIX );
475                         _logger.debug( "plugin resource 'file pattern' found : " + file.getPath(  ) );
476 
477                         break;
478 
479                     default:
480                         break;
481                 }
482 
483                 listResources.add( resource );
484             }
485             else
486             {
487                 _logger.debug( "plugin resource not found : " + file.getPath(  ) );
488             }
489         }
490         catch ( Exception e )
491         {
492             _logger.error( "PluginManagerService:AddResource error : " + e.getMessage(  ), e );
493         }
494     }
495 
496     /**
497      * Install a plugin
498      * @param strPluginName The plugin name
499      * @throws UpdaterInstallException If an install exception occurs
500      */
501     private void installPlugin( String strPluginName )
502         throws UpdaterInstallException
503     {
504         _logger.info( "install plugin : " + strPluginName );
505 
506         try
507         {
508             // Copy webapp's deployment data into the webapp
509             File fileSource = new File( getWebAppPath(  ) + PATH_DEPLOY + strPluginName + FOLDER_WEBAPP );
510             File fileDest = new File( getWebAppPath(  ) );
511 
512             FileUtils.copyDirectory( fileSource, fileDest );
513 
514             // Execute sql scripts
515             executeScripts( getWebAppPath(  ) + PATH_DEPLOY + strPluginName + FOLDER_SQL );
516 
517             // If the installation is OK, then delete deployment data
518             File fileDeploy = new File( getWebAppPath(  ) + PATH_DEPLOY + strPluginName );
519             FileUtils.deleteDirectory( fileDeploy );
520         }
521         catch ( IOException ex )
522         {
523             _logger.error( "error installing plugin : ", ex );
524             throw new UpdaterInstallException( "Installation exception", ex );
525         }
526         catch ( UpdaterScriptException ex )
527         {
528             throw new UpdaterInstallException( "Installation exception", ex );
529         }
530     }
531 }