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.version.InvalidVersionException;
37  import fr.paris.lutece.plugins.updater.business.version.Version;
38  import fr.paris.lutece.plugins.updater.service.catalog.CatalogInfos;
39  import fr.paris.lutece.plugins.updater.service.catalog.ICatalogService;
40  import fr.paris.lutece.plugins.updater.service.catalog.UpgradeInfos;
41  import fr.paris.lutece.portal.service.init.AppInfo;
42  import fr.paris.lutece.portal.service.plugin.Plugin;
43  import fr.paris.lutece.portal.service.spring.SpringContextService;
44  import fr.paris.lutece.portal.service.util.AppLogService;
45  import fr.paris.lutece.portal.service.util.AppPathService;
46  import fr.paris.lutece.util.httpaccess.HttpAccess;
47  import fr.paris.lutece.util.httpaccess.HttpAccessException;
48  
49  import org.apache.commons.io.FileUtils;
50  
51  import java.io.BufferedInputStream;
52  import java.io.BufferedOutputStream;
53  import java.io.File;
54  import java.io.FileOutputStream;
55  import java.io.IOException;
56  import java.io.InputStream;
57  import java.io.OutputStream;
58  
59  import java.util.ArrayList;
60  import java.util.Collection;
61  import java.util.Enumeration;
62  import java.util.List;
63  import java.util.zip.ZipEntry;
64  import java.util.zip.ZipException;
65  import java.util.zip.ZipFile;
66  
67  
68  /**
69   * UpdateService
70   */
71  public class UpdateService implements IUpdateService
72  {
73      private static final String PATH_DOWNLOADED = "/plugins/updater/downloaded/";
74      private static final String FOLDER_WEBAPP = "/webapp";
75      private static final String FOLDER_SQL = "/sql";
76      private static final String PLUGIN_NAME = "updater";
77      private static final String BEAN_CATALOG_SERVICE = "updater.catalogService";
78      private static Version _currentCoreVersion;
79      private static int _nStatus;
80      private static int _nRegularUpdateCount;
81      private static int _nCriticalUpdateCount;
82  
83      /**
84       * Gets available update for a list of plugins
85       * @param listPlugins The list of installed plugins
86       * @return Update infos for installed plugins
87       */
88      @Override
89      public List<UpdateInfos> getUpdateInfos( Collection<Plugin> listPlugins )
90      {
91          ICatalogService catalogService = (ICatalogService) SpringContextService.getBean( BEAN_CATALOG_SERVICE );
92          List<CatalogInfos> listCatalogInfos = catalogService.getCatalogInfos(  );
93          List<UpdateInfos> listUpdatesInfos = new ArrayList<UpdateInfos>(  );
94  
95          if ( listPlugins != null )
96          {
97              for ( Plugin plugin : listPlugins )
98              {
99                  for ( CatalogInfos ci : listCatalogInfos )
100                 {
101                     if ( ci.getPluginName(  ).equals( plugin.getName(  ) ) )
102                     {
103                         try
104                         {
105                             Version vCurrent = Version.parse( plugin.getVersion(  ) );
106                             Version vRepository = Version.parse( ci.getVersion(  ) );
107 
108                             // Display only upgrade for plugins that have a more recent version number
109                             if ( vRepository.compareTo( vCurrent ) > 0 )
110                             {
111                                 for ( UpgradeInfos upgrade : ci.getUpgrades(  ) )
112                                 {
113                                     // Find upgrade corresponding to the current version
114                                     if ( upgrade.getVersionFrom(  ).equals( plugin.getVersion(  ) ) &&
115                                             isCompliantWithCurrentCore( ci ) )
116                                     {
117                                         UpdateInfos ui = new UpdateInfos( ci.getPluginName(  ) );
118                                         ui.setCurrentVersion( plugin.getVersion(  ) );
119                                         ui.setTargetVersion( ci.getVersion(  ) );
120                                         ui.setCriticalUpdate( upgrade.getCriticalUpdate(  ) );
121                                         ui.setDownloaded( checkDownloaded( ci.getPluginName(  ), ci.getVersion(  ) ) );
122                                         ui.setInstallInProgress( PluginManagerService.getInstance(  )
123                                                                                      .checkInstallInProgress( ci.getPluginName(  ) ) );
124                                         listUpdatesInfos.add( ui );
125                                     }
126                                 }
127                             }
128                         }
129                         catch ( InvalidVersionException ex )
130                         {
131                             AppLogService.error( "Invalid version number for plugin : " + ci.getPluginName(  ), ex );
132                         }
133                     }
134                 }
135             }
136         }
137 
138         return listUpdatesInfos;
139     }
140 
141     /**
142      * Gets available plugins not already installed
143      * @param listPlugins The list of installed plugins
144      * @return New plugins infos list
145      */
146     @Override
147     public List<NewInfos> getNewPluginsInfos( Collection<Plugin> listPlugins )
148     {
149         ICatalogService catalogService = (ICatalogService) SpringContextService.getPluginBean( PLUGIN_NAME, BEAN_CATALOG_SERVICE );
150         List<CatalogInfos> listCatalogInfos = catalogService.getCatalogInfos(  );
151         List<NewInfos> listNewPluginInfos = new ArrayList<NewInfos>(  );
152 
153         for ( CatalogInfos ci : listCatalogInfos )
154         {
155             if ( !isInstalled( ci.getPluginName(  ), listPlugins ) && isCompliantWithCurrentCore( ci ) )
156             {
157                 NewInfos ni = new NewInfos( ci.getPluginName(  ) );
158                 ni.setDescription( ci.getDescription(  ) );
159                 ni.setVersion( ci.getVersion(  ) );
160                 ni.setAuthor( ci.getAuthor(  ) );
161                 ni.setHomepageUrl( ci.getHomepageUrl(  ) );
162                 ni.setDownloaded( checkDownloaded( ci.getPluginName(  ), ci.getVersion(  ) ) );
163                 ni.setInstallInProgress( PluginManagerService.getInstance(  )
164                                                              .checkInstallInProgress( ci.getPluginName(  ) ) );
165                 listNewPluginInfos.add( ni );
166             }
167         }
168 
169         return listNewPluginInfos;
170     }
171 
172     /**
173      * Deploy a plugin (downloaded => deploy)
174      * @param strPluginName The plugin name
175      * @param strVersion The update version
176      */
177     @Override
178     public void deployPlugin( String strPluginName, String strVersion )
179     {
180         AppLogService.info( "deploy plugin : " + strPluginName );
181 
182         try
183         {
184             // Copy the webapp folder
185             File fileSource = new File( AppPathService.getWebAppPath(  ) + PATH_DOWNLOADED + strPluginName + "/" +
186                     strVersion + FOLDER_WEBAPP );
187             File fileDest = new File( PluginManagerService.getInstance(  ).getDeployWebappPath( strPluginName ) );
188 
189             if ( fileDest.exists(  ) )
190             {
191                 FileUtils.deleteDirectory( fileDest );
192             }
193 
194             FileUtils.copyDirectory( fileSource, fileDest );
195 
196             // Copy the SQL folder
197             fileSource = new File( AppPathService.getWebAppPath(  ) + PATH_DOWNLOADED + strPluginName + "/" +
198                     strVersion + FOLDER_SQL );
199             fileDest = new File( PluginManagerService.getInstance(  ).getDeploySqlPath( strPluginName ) );
200 
201             if ( fileDest.exists(  ) )
202             {
203                 FileUtils.deleteDirectory( fileDest );
204             }
205 
206             FileUtils.copyDirectory( fileSource, fileDest );
207         }
208         catch ( IOException ex )
209         {
210             AppLogService.error( "error deploying plugin : ", ex );
211         }
212     }
213 
214     /**
215      * Checks if the plugin's catalog info is compliant with the current core version
216      * @param ci CatalogInfos
217      * @return true if OK, otherwise false
218      */
219     boolean isCompliantWithCurrentCore( CatalogInfos ci )
220     {
221         if ( _currentCoreVersion == null )
222         {
223             try
224             {
225                 _currentCoreVersion = Version.parse( AppInfo.getVersion(  ) );
226             }
227             catch ( InvalidVersionException ex )
228             {
229                 AppLogService.error( "Invalid core version ", ex );
230 
231                 return false;
232             }
233         }
234 
235         try
236         {
237             // Checks that the current core version is higher than the min required
238             Version requiredMinCoreVersion = Version.parse( ci.getCoreVersionMin(  ) );
239 
240             if ( _currentCoreVersion.compareTo( requiredMinCoreVersion ) < 0 )
241             {
242                 return false;
243             }
244 
245             // Checks that the current core version is lower than the max required
246             if ( ci.getCoreVersionMax(  ) != null )
247             {
248                 Version requiredMaxCoreVersion = Version.parse( ci.getCoreVersionMax(  ) );
249 
250                 if ( _currentCoreVersion.compareTo( requiredMaxCoreVersion ) > 0 )
251                 {
252                     return false;
253                 }
254             }
255         }
256         catch ( InvalidVersionException ex )
257         {
258             AppLogService.error( "Invalid version : " + ci.getPluginName(  ), ex );
259 
260             return false;
261         }
262 
263         return true;
264     }
265 
266     /**
267      * Returns the updater status : (no update available, regular updates
268      * available or critical updates available.
269      * @return The status
270      */
271     @Override
272     public int getStatus(  )
273     {
274         return _nStatus;
275     }
276     
277     /**
278      * Returns the number of regular updates available
279      * @return the number of regular updates available
280      */
281     @Override
282     public int getRegularUpdateCount()
283     {
284         return _nRegularUpdateCount;
285     }
286     
287     /**
288      * Returns the number of critical updates available
289      * @return the number of critical updates available
290      */
291     @Override
292     public int getCriticalUpdateCount()
293     {
294         return _nCriticalUpdateCount;
295     }
296 
297     /**
298      * Check for updates and update the status
299      * @param listPlugins The list of installed plugins
300      */
301     @Override
302     public void checkUpdate( Collection<Plugin> listPlugins )
303     {
304         _nStatus = STATUS_NO_UPDATE;
305         _nRegularUpdateCount = 0;
306         _nCriticalUpdateCount = 0;
307 
308         List<UpdateInfos> listUpdatesInfos = getUpdateInfos( listPlugins );
309 
310         if ( listUpdatesInfos.size(  ) > 0 )
311         {
312             _nStatus = STATUS_REGULAR_UPDATE;
313 
314             for ( UpdateInfos ui : listUpdatesInfos )
315             {
316                 if ( ui.isCriticalUpdate(  ) )
317                 {
318                     _nStatus = STATUS_CRITICAL_UPDATE;
319                     _nCriticalUpdateCount++;
320                 }
321                 else
322                 {
323                     _nRegularUpdateCount++;
324                 }
325             }
326         }
327     }
328 
329     /**
330      * Tells if a plugin is among of an installed plugin list
331      * @param strPluginName The plugin name
332      * @param listPlugins The installed plugins list
333      * @return true if the plugin is in the list
334      */
335     private boolean isInstalled( String strPluginName, Collection<Plugin> listPlugins )
336     {
337         for ( Plugin plugin : listPlugins )
338         {
339             if ( strPluginName.equals( plugin.getName(  ) ) )
340             {
341                 return true;
342             }
343         }
344 
345         return false;
346     }
347 
348     /**
349      * Checks if the release has been downloaded
350      * @param strPluginName The plugin name
351      * @param strVersion The release version
352      * @return true if the release has been downloaded, otherwise false
353      */
354     private boolean checkDownloaded( String strPluginName, String strVersion )
355     {
356         String strReleasesDirectory = AppPathService.getWebAppPath(  ) + PATH_DOWNLOADED + strPluginName + "/" +
357             strVersion;
358         File fReleasesDirectory = new File( strReleasesDirectory );
359 
360         return fReleasesDirectory.exists(  );
361     }
362 
363     /**
364      * Download a plugin release
365      * @param strPluginName The plugin name
366      * @param  strVersion The version
367      * @throws UpdaterDownloadException If an exception occurs during download
368      */
369     @Override
370     public void downloadPlugin( String strPluginName, String strVersion )
371         throws UpdaterDownloadException
372     {
373         CatalogInfos ci = getCatalogInfos( strPluginName );
374 
375         if ( ci != null )
376         {
377             downloadPackage( strPluginName, strVersion, ci.getDownloadUrl(  ) );
378         }
379     }
380 
381     /**
382      * Download a plugin release
383      * @param strPluginName The plugin name
384      * @param  strVersion The version
385      * @param  strVersionFrom The version from which to upgrade
386      * @throws UpdaterDownloadException If an exception occurs during download
387     */
388     @Override
389     public void downloadPluginUpgrade( String strPluginName, String strVersion, String strVersionFrom )
390         throws UpdaterDownloadException
391     {
392         CatalogInfos ci = getCatalogInfos( strPluginName );
393 
394         if ( ci != null )
395         {
396             for ( UpgradeInfos ui : ci.getUpgrades(  ) )
397             {
398                 if ( ui.getVersionFrom(  ).equals( strVersionFrom ) )
399                 {
400                     downloadPackage( strPluginName, strVersion, ui.getDownloadUrl(  ) );
401                 }
402             }
403         }
404     }
405 
406     /**
407      * Download a package for a given plugin and a given version
408      * @param strPluginName The plugin name
409      * @param strVersion The version
410      * @param strPackageFileUrl The package download url
411      * @throws UpdaterDownloadException If an exception occurs during download
412      */
413     private void downloadPackage( String strPluginName, String strVersion, String strPackageFileUrl )
414         throws UpdaterDownloadException
415     {
416         try
417         {
418             HttpAccess httpAccess = new HttpAccess(  );
419             String strPluginDirectory = AppPathService.getWebAppPath(  ) + PATH_DOWNLOADED + strPluginName;
420             File directory = new File( strPluginDirectory );
421 
422             if ( !directory.exists(  ) )
423             {
424                 FileUtils.forceMkdir( directory );
425             }
426 
427             String strPackageFile = strPluginDirectory + "/" + strPluginName + ".zip";
428             httpAccess.downloadFile( strPackageFileUrl, strPackageFile );
429 
430             String strVersionDirectory = strPluginDirectory + "/" + strVersion;
431             extractPackage( strPackageFile, strVersionDirectory );
432         }
433         catch ( HttpAccessException e )
434         {
435             AppLogService.error( "Error downloading file : " + e.getMessage(  ), e );
436             throw new UpdaterDownloadException( "Error downloading file : " + e.getMessage(  ), e );
437         }
438         catch ( IOException e )
439         {
440             AppLogService.error( "Error creating downloaded file : " + e.getMessage(  ), e );
441             throw new UpdaterDownloadException( "Error creating downloaded file : " + e.getMessage(  ), e );
442         }
443     }
444 
445     /**
446      * Extract a package
447      * @param strZipFile The package zip file
448      * @param strDirectory The target directory
449      * @throws UpdaterDownloadException If an exception occurs during download
450      */
451     private void extractPackage( String strZipFile, String strDirectory )
452         throws UpdaterDownloadException
453     {
454         try
455         {
456             File file = new File( strZipFile );
457             ZipFile zipFile = new ZipFile( file );
458 
459             // Each zipped file is indentified by a zip entry :
460             Enumeration zipEntries = zipFile.entries(  );
461 
462             while ( zipEntries.hasMoreElements(  ) )
463             {
464                 ZipEntry zipEntry = (ZipEntry) zipEntries.nextElement(  );
465 
466                 // Clean the name :
467                 String strEntryName = zipEntry.getName(  );
468 
469                 // The unzipped file :
470                 File destFile = new File( strDirectory, strEntryName );
471 
472                 // Create the parent directory structure if needed :
473                 destFile.getParentFile(  ).mkdirs(  );
474 
475                 if ( !zipEntry.isDirectory(  ) )
476                 {
477                     // InputStream from zipped data
478                     InputStream inZipStream = null;
479 
480                     try
481                     {
482                         AppLogService.debug( "unzipping " + strEntryName + " to " + destFile.getName(  ) );
483                         inZipStream = zipFile.getInputStream( zipEntry );
484 
485                         // OutputStream to the destination file
486                         OutputStream outDestStream = new FileOutputStream( destFile );
487 
488                         // Helper method to copy data
489                         copyStream( inZipStream, outDestStream );
490 
491                         inZipStream.close(  );
492                         outDestStream.close(  );
493                     }
494                     catch ( IOException e )
495                     {
496                         AppLogService.error( "Error extracting file : " + e.getMessage(  ), e );
497                     }
498                     finally
499                     {
500                         try
501                         {
502                             inZipStream.close(  );
503                         }
504                         catch ( IOException e )
505                         {
506                             AppLogService.error( "Error extracting file : " + e.getMessage(  ), e );
507                         }
508                     }
509                 }
510                 else
511                 {
512                     AppLogService.debug( "skipping directory " + strEntryName );
513                 }
514             }
515         }
516         catch ( ZipException e )
517         {
518             AppLogService.error( "Error extracting file : " + e.getMessage(  ), e );
519             throw new UpdaterDownloadException( "Error extracting package ", e );
520         }
521         catch ( IOException e )
522         {
523             AppLogService.error( "Error extracting file : " + e.getMessage(  ), e );
524         }
525     }
526 
527     /**
528      * Copies data from an input stream to an output stream.
529      * @param inStream The input stream
530      * @param outStream The output stream
531      * @throws IOException If an I/O error occurs
532      */
533     private static void copyStream( InputStream inStream, OutputStream outStream )
534         throws IOException
535     {
536         BufferedInputStream inBufferedStream = new BufferedInputStream( inStream );
537         BufferedOutputStream outBufferedStream = new BufferedOutputStream( outStream );
538 
539         int nByte;
540 
541         while ( ( nByte = inBufferedStream.read(  ) ) > -1 )
542         {
543             outBufferedStream.write( nByte );
544         }
545 
546         outBufferedStream.close(  );
547         inBufferedStream.close(  );
548     }
549 
550     /**
551      * Get catalog infos for a given plugin
552      * @param strPluginName The plugin name
553      * @return A CatalogInfos object
554      */
555     private CatalogInfos getCatalogInfos( String strPluginName )
556     {
557         ICatalogService catalogService = (ICatalogService) SpringContextService.getPluginBean( PLUGIN_NAME, BEAN_CATALOG_SERVICE );
558         List<CatalogInfos> listCatalogInfos = catalogService.getCatalogInfos(  );
559 
560         for ( CatalogInfos ci : listCatalogInfos )
561         {
562             if ( ci.getPluginName(  ).equals( strPluginName ) )
563             {
564                 return ci;
565             }
566         }
567 
568         return null;
569     }
570 }