2020-03-23 15:35:58 +01:00
/ *
* MIT License
2010-10-31 19:22:53 +01:00
*
2020-03-23 15:35:58 +01:00
* Copyright ( c ) 2020 - present Cloudogu GmbH and Contributors
2010-10-31 19:22:53 +01:00
*
2020-03-23 15:35:58 +01:00
* Permission is hereby granted , free of charge , to any person obtaining a copy
* of this software and associated documentation files ( the " Software " ) , to deal
* in the Software without restriction , including without limitation the rights
* to use , copy , modify , merge , publish , distribute , sublicense , and / or sell
* copies of the Software , and to permit persons to whom the Software is
* furnished to do so , subject to the following conditions :
2010-10-31 19:22:53 +01:00
*
2020-03-23 15:35:58 +01:00
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software .
2010-10-31 19:22:53 +01:00
*
2020-03-23 15:35:58 +01:00
* THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
* IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER
* LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING FROM ,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE .
2010-10-12 09:16:40 +02:00
* /
2020-04-02 10:43:07 +02:00
2010-12-01 14:26:29 +01:00
package sonia.scm.plugin ;
2010-10-12 09:16:40 +02:00
2019-09-16 13:22:26 +02:00
import com.google.common.annotations.VisibleForTesting ;
2019-08-20 12:29:59 +02:00
import com.google.common.collect.ImmutableList ;
2010-12-13 18:59:00 +01:00
import com.google.inject.Singleton ;
2019-08-20 14:43:48 +02:00
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
import sonia.scm.NotFoundException ;
2019-08-21 11:22:49 +02:00
import sonia.scm.event.ScmEventBus ;
2020-02-12 14:45:13 +01:00
import sonia.scm.lifecycle.Restarter ;
2019-09-11 14:51:38 +02:00
import sonia.scm.version.Version ;
2010-12-13 18:59:00 +01:00
2019-08-20 12:29:59 +02:00
import javax.inject.Inject ;
2019-09-16 13:22:26 +02:00
import java.nio.file.Files ;
2019-09-26 17:50:54 +02:00
import java.nio.file.Path ;
2019-08-21 08:42:57 +02:00
import java.util.ArrayList ;
2019-09-16 13:22:26 +02:00
import java.util.Collection ;
2019-08-20 10:33:57 +02:00
import java.util.List ;
import java.util.Optional ;
2019-08-20 14:43:48 +02:00
import java.util.Set ;
2020-08-05 15:28:39 +02:00
import java.util.function.Function ;
2019-08-20 12:29:59 +02:00
import java.util.function.Predicate ;
import java.util.stream.Collectors ;
2019-07-30 16:49:24 +02:00
2019-08-20 14:43:48 +02:00
import static sonia.scm.ContextEntry.ContextBuilder.entity ;
2019-09-11 16:46:27 +02:00
import static sonia.scm.ScmConstraintViolationException.Builder.doThrow ;
2019-08-20 14:43:48 +02:00
2019-09-16 13:22:26 +02:00
//~--- JDK imports ------------------------------------------------------------
2010-10-12 09:16:40 +02:00
/ * *
* @author Sebastian Sdorra
* /
2010-12-13 18:59:00 +01:00
@Singleton
2019-08-20 10:33:57 +02:00
public class DefaultPluginManager implements PluginManager {
2010-12-13 18:59:00 +01:00
2019-08-20 14:43:48 +02:00
private static final Logger LOG = LoggerFactory . getLogger ( DefaultPluginManager . class ) ;
2019-08-20 12:29:59 +02:00
private final PluginLoader loader ;
private final PluginCenter center ;
2019-08-20 14:43:48 +02:00
private final PluginInstaller installer ;
2020-02-12 14:45:13 +01:00
private final Restarter restarter ;
2020-04-01 16:01:26 +02:00
private final ScmEventBus eventBus ;
2020-02-12 14:45:13 +01:00
2019-09-26 17:50:54 +02:00
private final Collection < PendingPluginInstallation > pendingInstallQueue = new ArrayList < > ( ) ;
private final Collection < PendingPluginUninstallation > pendingUninstallQueue = new ArrayList < > ( ) ;
2019-09-16 13:22:26 +02:00
private final PluginDependencyTracker dependencyTracker = new PluginDependencyTracker ( ) ;
2019-08-20 12:29:59 +02:00
2020-08-11 08:07:06 +02:00
private final Function < List < AvailablePlugin > , PluginInstallationContext > contextFactory ;
2020-08-05 15:28:39 +02:00
2019-08-20 12:29:59 +02:00
@Inject
2020-04-01 16:01:26 +02:00
public DefaultPluginManager ( PluginLoader loader , PluginCenter center , PluginInstaller installer , Restarter restarter , ScmEventBus eventBus ) {
2020-08-11 08:07:06 +02:00
this ( loader , center , installer , restarter , eventBus , null ) ;
}
DefaultPluginManager ( PluginLoader loader , PluginCenter center , PluginInstaller installer , Restarter restarter , ScmEventBus eventBus , Function < List < AvailablePlugin > , PluginInstallationContext > contextFactory ) {
2019-08-20 12:29:59 +02:00
this . loader = loader ;
this . center = center ;
2019-08-20 14:43:48 +02:00
this . installer = installer ;
2020-02-12 14:45:13 +01:00
this . restarter = restarter ;
2020-04-01 16:01:26 +02:00
this . eventBus = eventBus ;
2019-09-16 13:22:26 +02:00
2020-08-11 08:07:06 +02:00
if ( contextFactory ! = null ) {
this . contextFactory = contextFactory ;
} else {
2020-08-25 12:41:07 +02:00
this . contextFactory = ( plugins - > {
List < AvailablePlugin > pendingPlugins = new ArrayList < > ( plugins ) ;
pendingInstallQueue . stream ( ) . map ( PendingPluginInstallation : : getPlugin ) . forEach ( pendingPlugins : : add ) ;
return PluginInstallationContext . from ( getInstalled ( ) , pendingPlugins ) ;
} ) ;
2020-08-11 08:07:06 +02:00
}
2019-09-16 17:50:05 +02:00
this . computeInstallationDependencies ( ) ;
2020-08-05 15:28:39 +02:00
}
2019-09-16 13:22:26 +02:00
@VisibleForTesting
2019-09-16 17:50:05 +02:00
synchronized void computeInstallationDependencies ( ) {
2019-09-16 13:22:26 +02:00
loader . getInstalledPlugins ( )
. stream ( )
. map ( InstalledPlugin : : getDescriptor )
. forEach ( dependencyTracker : : addInstalled ) ;
2019-09-16 17:50:05 +02:00
updateMayUninstallFlag ( ) ;
2019-08-20 12:29:59 +02:00
}
2010-12-18 14:18:14 +01:00
@Override
2019-08-20 10:33:57 +02:00
public Optional < AvailablePlugin > getAvailable ( String name ) {
2019-08-21 09:25:44 +02:00
PluginPermissions . read ( ) . check ( ) ;
2019-08-20 12:29:59 +02:00
return center . getAvailable ( )
. stream ( )
. filter ( filterByName ( name ) )
2019-09-11 14:51:38 +02:00
. filter ( this : : isNotInstalledOrMoreUpToDate )
2019-08-21 12:49:15 +02:00
. map ( p - > getPending ( name ) . orElse ( p ) )
. findFirst ( ) ;
}
private Optional < AvailablePlugin > getPending ( String name ) {
2019-09-26 17:50:54 +02:00
return pendingInstallQueue
2019-08-21 12:49:15 +02:00
. stream ( )
. map ( PendingPluginInstallation : : getPlugin )
. filter ( filterByName ( name ) )
2019-08-20 12:29:59 +02:00
. findFirst ( ) ;
2011-09-06 12:17:29 +02:00
}
2010-12-01 14:26:29 +01:00
@Override
2019-08-20 10:33:57 +02:00
public Optional < InstalledPlugin > getInstalled ( String name ) {
2019-08-21 09:25:44 +02:00
PluginPermissions . read ( ) . check ( ) ;
2019-08-20 12:29:59 +02:00
return loader . getInstalledPlugins ( )
. stream ( )
. filter ( filterByName ( name ) )
. findFirst ( ) ;
2010-12-13 18:59:00 +01:00
}
2010-12-01 14:26:29 +01:00
2012-09-29 21:44:39 +02:00
@Override
2019-08-20 10:33:57 +02:00
public List < InstalledPlugin > getInstalled ( ) {
2019-08-21 09:25:44 +02:00
PluginPermissions . read ( ) . check ( ) ;
2019-08-20 12:29:59 +02:00
return ImmutableList . copyOf ( loader . getInstalledPlugins ( ) ) ;
2012-09-29 21:44:39 +02:00
}
2010-12-13 18:59:00 +01:00
@Override
2019-08-20 10:33:57 +02:00
public List < AvailablePlugin > getAvailable ( ) {
2019-08-21 09:25:44 +02:00
PluginPermissions . read ( ) . check ( ) ;
2019-08-21 12:49:15 +02:00
return center . getAvailable ( )
. stream ( )
2019-09-11 14:51:38 +02:00
. filter ( this : : isNotInstalledOrMoreUpToDate )
2019-08-21 12:49:15 +02:00
. map ( p - > getPending ( p . getDescriptor ( ) . getInformation ( ) . getName ( ) ) . orElse ( p ) )
. collect ( Collectors . toList ( ) ) ;
2019-08-20 12:29:59 +02:00
}
2019-09-27 16:07:25 +02:00
@Override
public List < InstalledPlugin > getUpdatable ( ) {
return getInstalled ( )
. stream ( )
. filter ( p - > isUpdatable ( p . getDescriptor ( ) . getInformation ( ) . getName ( ) ) )
2019-09-28 15:28:40 +02:00
. filter ( p - > ! p . isMarkedForUninstall ( ) )
2019-09-27 16:07:25 +02:00
. collect ( Collectors . toList ( ) ) ;
}
2019-08-20 12:29:59 +02:00
private < T extends Plugin > Predicate < T > filterByName ( String name ) {
2019-08-21 08:42:57 +02:00
return plugin - > name . equals ( plugin . getDescriptor ( ) . getInformation ( ) . getName ( ) ) ;
2019-08-20 12:29:59 +02:00
}
2019-09-11 14:51:38 +02:00
private boolean isNotInstalledOrMoreUpToDate ( AvailablePlugin availablePlugin ) {
return getInstalled ( availablePlugin . getDescriptor ( ) . getInformation ( ) . getName ( ) )
. map ( installedPlugin - > availableIsMoreUpToDateThanInstalled ( availablePlugin , installedPlugin ) )
. orElse ( true ) ;
}
private boolean availableIsMoreUpToDateThanInstalled ( AvailablePlugin availablePlugin , InstalledPlugin installed ) {
return Version . parse ( availablePlugin . getDescriptor ( ) . getInformation ( ) . getVersion ( ) ) . isNewer ( installed . getDescriptor ( ) . getInformation ( ) . getVersion ( ) ) ;
2010-10-12 09:16:40 +02:00
}
2010-12-18 13:37:34 +01:00
@Override
2019-08-21 11:22:49 +02:00
public void install ( String name , boolean restartAfterInstallation ) {
2020-05-05 10:55:23 +02:00
PluginPermissions . write ( ) . check ( ) ;
2019-09-11 16:46:27 +02:00
getInstalled ( name )
. map ( InstalledPlugin : : isCore )
. ifPresent (
core - > doThrow ( ) . violation ( " plugin is a core plugin and cannot be updated " ) . when ( core )
) ;
2019-08-21 08:42:57 +02:00
List < AvailablePlugin > plugins = collectPluginsToInstall ( name ) ;
List < PendingPluginInstallation > pendingInstallations = new ArrayList < > ( ) ;
2020-08-05 15:28:39 +02:00
2019-08-21 08:42:57 +02:00
for ( AvailablePlugin plugin : plugins ) {
try {
2020-08-05 15:28:39 +02:00
PendingPluginInstallation pending = installer . install ( contextFactory . apply ( plugins ) , plugin ) ;
2019-09-16 17:50:05 +02:00
dependencyTracker . addInstalled ( plugin . getDescriptor ( ) ) ;
2019-08-21 08:42:57 +02:00
pendingInstallations . add ( pending ) ;
2020-04-02 10:43:07 +02:00
eventBus . post ( new PluginEvent ( PluginEvent . PluginEventType . INSTALLED , plugin ) ) ;
2020-11-24 18:18:16 +01:00
} catch ( PluginInstallException installException ) {
try {
cancelPending ( pendingInstallations ) ;
} catch ( PluginFailedToCancelInstallationException cancelInstallationException ) {
LOG . error ( " could not install plugin {}; uninstallation failed (see next exception) " , plugin . getDescriptor ( ) . getInformation ( ) . getName ( ) , installException ) ;
throw cancelInstallationException ;
} finally {
eventBus . post ( new PluginEvent ( PluginEvent . PluginEventType . INSTALLATION_FAILED , plugin ) ) ;
}
throw installException ;
2019-08-20 14:43:48 +02:00
}
}
2019-08-21 12:49:15 +02:00
if ( ! pendingInstallations . isEmpty ( ) ) {
if ( restartAfterInstallation ) {
2019-11-25 08:15:13 +01:00
triggerRestart ( " plugin installation " ) ;
2019-08-21 12:49:15 +02:00
} else {
2019-09-26 17:50:54 +02:00
pendingInstallQueue . addAll ( pendingInstallations ) ;
2019-09-16 17:50:05 +02:00
updateMayUninstallFlag ( ) ;
2019-08-21 12:49:15 +02:00
}
2019-08-21 11:22:49 +02:00
}
2019-08-21 08:42:57 +02:00
}
2019-09-16 13:22:26 +02:00
@Override
public void uninstall ( String name , boolean restartAfterInstallation ) {
2020-05-05 10:55:23 +02:00
PluginPermissions . write ( ) . check ( ) ;
2019-09-16 13:22:26 +02:00
InstalledPlugin installed = getInstalled ( name )
. orElseThrow ( ( ) - > NotFoundException . notFound ( entity ( InstalledPlugin . class , name ) ) ) ;
2019-09-16 14:12:49 +02:00
doThrow ( ) . violation ( " plugin is a core plugin and cannot be uninstalled " ) . when ( installed . isCore ( ) ) ;
2019-09-16 13:22:26 +02:00
2019-09-26 17:50:54 +02:00
markForUninstall ( installed ) ;
2019-09-16 17:50:05 +02:00
if ( restartAfterInstallation ) {
2019-11-25 08:15:13 +01:00
triggerRestart ( " plugin installation " ) ;
2019-09-16 17:50:05 +02:00
} else {
updateMayUninstallFlag ( ) ;
}
}
private void updateMayUninstallFlag ( ) {
loader . getInstalledPlugins ( )
. forEach ( p - > p . setUninstallable ( isUninstallable ( p ) ) ) ;
}
private boolean isUninstallable ( InstalledPlugin p ) {
return ! p . isCore ( )
& & ! p . isMarkedForUninstall ( )
& & dependencyTracker . mayUninstall ( p . getDescriptor ( ) . getInformation ( ) . getName ( ) ) ;
2019-09-16 13:22:26 +02:00
}
2019-09-26 17:50:54 +02:00
private void markForUninstall ( InstalledPlugin plugin ) {
dependencyTracker . removeInstalled ( plugin . getDescriptor ( ) ) ;
2019-09-18 15:47:58 +02:00
try {
2019-09-26 17:50:54 +02:00
Path file = Files . createFile ( plugin . getDirectory ( ) . resolve ( InstalledPlugin . UNINSTALL_MARKER_FILENAME ) ) ;
pendingUninstallQueue . add ( new PendingPluginUninstallation ( plugin , file ) ) ;
plugin . setMarkedForUninstall ( true ) ;
} catch ( Exception e ) {
dependencyTracker . addInstalled ( plugin . getDescriptor ( ) ) ;
throw new PluginException ( " could not mark plugin " + plugin . getId ( ) + " in path " + plugin . getDirectory ( ) + " as " + InstalledPlugin . UNINSTALL_MARKER_FILENAME , e ) ;
2019-09-18 15:47:58 +02:00
}
}
2019-08-21 12:49:15 +02:00
@Override
2019-09-17 10:36:52 +02:00
public void executePendingAndRestart ( ) {
2020-05-05 10:55:23 +02:00
PluginPermissions . write ( ) . check ( ) ;
2019-09-26 17:50:54 +02:00
if ( ! pendingInstallQueue . isEmpty ( ) | | getInstalled ( ) . stream ( ) . anyMatch ( InstalledPlugin : : isMarkedForUninstall ) ) {
2019-11-25 08:15:13 +01:00
triggerRestart ( " execute pending plugin changes " ) ;
2019-08-21 12:49:15 +02:00
}
}
2020-02-12 14:45:13 +01:00
private void triggerRestart ( String cause ) {
restarter . restart ( PluginManager . class , cause ) ;
2019-08-21 12:49:15 +02:00
}
2019-08-21 08:42:57 +02:00
private void cancelPending ( List < PendingPluginInstallation > pendingInstallations ) {
pendingInstallations . forEach ( PendingPluginInstallation : : cancel ) ;
}
2019-08-20 14:43:48 +02:00
2019-08-21 08:42:57 +02:00
private List < AvailablePlugin > collectPluginsToInstall ( String name ) {
List < AvailablePlugin > plugins = new ArrayList < > ( ) ;
2020-07-02 12:08:23 +02:00
collectPluginsToInstallOrUpdate ( plugins , name ) ;
2019-08-21 08:42:57 +02:00
return plugins ;
}
2019-08-21 09:25:44 +02:00
2020-07-02 12:08:23 +02:00
private void collectPluginsToInstallOrUpdate ( List < AvailablePlugin > plugins , String name ) {
if ( ! isInstalledOrPending ( name ) | | isUpdatable ( name ) ) {
2020-07-21 15:17:40 +02:00
collectDependentPlugins ( plugins , name ) ;
} else {
LOG . info ( " plugin {} is already installed or installation is pending, skipping installation " , name ) ;
}
}
private void collectOptionalPluginToInstallOrUpdate ( List < AvailablePlugin > plugins , String name ) {
if ( isInstalledOrPending ( name ) & & isUpdatable ( name ) ) {
collectDependentPlugins ( plugins , name ) ;
} else {
LOG . info ( " optional plugin {} is not installed or not updatable " , name ) ;
}
}
private void collectDependentPlugins ( List < AvailablePlugin > plugins , String name ) {
AvailablePlugin plugin = getAvailable ( name ) . orElseThrow ( ( ) - > NotFoundException . notFound ( entity ( AvailablePlugin . class , name ) ) ) ;
2019-08-21 08:42:57 +02:00
2020-07-21 15:17:40 +02:00
Set < String > dependencies = plugin . getDescriptor ( ) . getDependencies ( ) ;
if ( dependencies ! = null ) {
for ( String dependency : dependencies ) {
collectPluginsToInstallOrUpdate ( plugins , dependency ) ;
2019-08-21 08:42:57 +02:00
}
2020-07-21 15:17:40 +02:00
}
2019-08-21 08:42:57 +02:00
2020-07-21 15:17:40 +02:00
Set < String > optionalDependencies = plugin . getDescriptor ( ) . getOptionalDependencies ( ) ;
if ( dependencies ! = null ) {
for ( String optionalDependency : optionalDependencies ) {
collectOptionalPluginToInstallOrUpdate ( plugins , optionalDependency ) ;
}
2019-08-21 08:42:57 +02:00
}
2020-07-21 15:17:40 +02:00
plugins . add ( plugin ) ;
2010-12-18 13:37:34 +01:00
}
2019-09-11 15:05:25 +02:00
private boolean isInstalledOrPending ( String name ) {
return getInstalled ( name ) . isPresent ( ) | | getPending ( name ) . isPresent ( ) ;
}
private boolean isUpdatable ( String name ) {
return getAvailable ( name ) . isPresent ( ) & & ! getPending ( name ) . isPresent ( ) ;
}
2019-09-26 17:50:54 +02:00
@Override
2019-09-27 11:46:14 +02:00
public void cancelPending ( ) {
2020-05-05 10:55:23 +02:00
PluginPermissions . write ( ) . check ( ) ;
2019-09-26 17:50:54 +02:00
pendingUninstallQueue . forEach ( PendingPluginUninstallation : : cancel ) ;
pendingInstallQueue . forEach ( PendingPluginInstallation : : cancel ) ;
2019-09-28 15:28:40 +02:00
pendingUninstallQueue . clear ( ) ;
pendingInstallQueue . clear ( ) ;
2019-10-01 09:33:48 +02:00
updateMayUninstallFlag ( ) ;
2019-09-26 17:50:54 +02:00
}
2019-09-27 15:30:21 +02:00
@Override
2019-09-28 11:44:39 +02:00
public void updateAll ( ) {
2020-05-05 10:55:23 +02:00
PluginPermissions . write ( ) . check ( ) ;
2019-09-27 15:30:21 +02:00
for ( InstalledPlugin installedPlugin : getInstalled ( ) ) {
String pluginName = installedPlugin . getDescriptor ( ) . getInformation ( ) . getName ( ) ;
if ( isUpdatable ( pluginName ) ) {
install ( pluginName , false ) ;
}
2019-09-28 11:44:39 +02:00
}
2019-09-27 15:30:21 +02:00
}
2010-10-12 09:16:40 +02:00
}