mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-14 01:15:44 +01:00
Merged in feature/install_plugins (pull request #299)
Feature/install plugins
This commit is contained in:
@@ -3,24 +3,22 @@ package sonia.scm.api.v2.resources;
|
||||
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||
import sonia.scm.plugin.Plugin;
|
||||
import sonia.scm.plugin.PluginInformation;
|
||||
import sonia.scm.plugin.AvailablePlugin;
|
||||
import sonia.scm.plugin.InstalledPluginDescriptor;
|
||||
import sonia.scm.plugin.PluginManager;
|
||||
import sonia.scm.plugin.PluginPermissions;
|
||||
import sonia.scm.plugin.PluginState;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
import static sonia.scm.NotFoundException.notFound;
|
||||
@@ -53,11 +51,8 @@ public class AvailablePluginResource {
|
||||
@Produces(VndMediaType.PLUGIN_COLLECTION)
|
||||
public Response getAvailablePlugins() {
|
||||
PluginPermissions.read().check();
|
||||
Collection<PluginInformation> plugins = pluginManager.getAvailable()
|
||||
.stream()
|
||||
.filter(plugin -> plugin.getState().equals(PluginState.AVAILABLE))
|
||||
.collect(Collectors.toList());
|
||||
return Response.ok(collectionMapper.map(plugins)).build();
|
||||
List<AvailablePlugin> available = pluginManager.getAvailable();
|
||||
return Response.ok(collectionMapper.mapAvailable(available)).build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,7 +61,7 @@ public class AvailablePluginResource {
|
||||
* @return available plugin.
|
||||
*/
|
||||
@GET
|
||||
@Path("/{name}/{version}")
|
||||
@Path("/{name}")
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 404, condition = "not found"),
|
||||
@@ -74,35 +69,42 @@ public class AvailablePluginResource {
|
||||
})
|
||||
@TypeHint(PluginDto.class)
|
||||
@Produces(VndMediaType.PLUGIN)
|
||||
public Response getAvailablePlugin(@PathParam("name") String name, @PathParam("version") String version) {
|
||||
public Response getAvailablePlugin(@PathParam("name") String name) {
|
||||
PluginPermissions.read().check();
|
||||
Optional<PluginInformation> plugin = pluginManager.getAvailable()
|
||||
.stream()
|
||||
.filter(p -> p.getId().equals(name + ":" + version))
|
||||
.findFirst();
|
||||
Optional<AvailablePlugin> plugin = pluginManager.getAvailable(name);
|
||||
if (plugin.isPresent()) {
|
||||
return Response.ok(mapper.map(plugin.get())).build();
|
||||
return Response.ok(mapper.mapAvailable(plugin.get())).build();
|
||||
} else {
|
||||
throw notFound(entity(Plugin.class, name));
|
||||
throw notFound(entity(InstalledPluginDescriptor.class, name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers plugin installation.
|
||||
* @param name plugin artefact name
|
||||
* @param version plugin version
|
||||
* @param name plugin name
|
||||
* @return HTTP Status.
|
||||
*/
|
||||
@POST
|
||||
@Path("/{name}/{version}/install")
|
||||
@Consumes(VndMediaType.PLUGIN)
|
||||
@Path("/{name}/install")
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
public Response installPlugin(@PathParam("name") String name, @PathParam("version") String version) {
|
||||
public Response installPlugin(@PathParam("name") String name, @QueryParam("restart") boolean restartAfterInstallation) {
|
||||
PluginPermissions.manage().check();
|
||||
pluginManager.install(name + ":" + version);
|
||||
pluginManager.install(name, restartAfterInstallation);
|
||||
return Response.ok().build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/install-pending")
|
||||
@StatusCodes({
|
||||
@ResponseCode(code = 200, condition = "success"),
|
||||
@ResponseCode(code = 500, condition = "internal server error")
|
||||
})
|
||||
public Response installPending() {
|
||||
PluginPermissions.manage().check();
|
||||
pluginManager.installPendingAndRestart();
|
||||
return Response.ok().build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,10 @@ package sonia.scm.api.v2.resources;
|
||||
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||
import sonia.scm.plugin.Plugin;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.plugin.InstalledPlugin;
|
||||
import sonia.scm.plugin.InstalledPluginDescriptor;
|
||||
import sonia.scm.plugin.PluginManager;
|
||||
import sonia.scm.plugin.PluginPermissions;
|
||||
import sonia.scm.plugin.PluginWrapper;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -16,7 +15,6 @@ import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -25,17 +23,15 @@ import static sonia.scm.NotFoundException.notFound;
|
||||
|
||||
public class InstalledPluginResource {
|
||||
|
||||
private final PluginLoader pluginLoader;
|
||||
private final PluginDtoCollectionMapper collectionMapper;
|
||||
private final PluginDtoMapper mapper;
|
||||
private final PluginManager pluginManager;
|
||||
|
||||
@Inject
|
||||
public InstalledPluginResource(PluginLoader pluginLoader, PluginDtoCollectionMapper collectionMapper, PluginDtoMapper mapper, PluginManager pluginManager) {
|
||||
this.pluginLoader = pluginLoader;
|
||||
public InstalledPluginResource(PluginManager pluginManager, PluginDtoCollectionMapper collectionMapper, PluginDtoMapper mapper) {
|
||||
this.pluginManager = pluginManager;
|
||||
this.collectionMapper = collectionMapper;
|
||||
this.mapper = mapper;
|
||||
this.pluginManager = pluginManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,8 +49,8 @@ public class InstalledPluginResource {
|
||||
@Produces(VndMediaType.PLUGIN_COLLECTION)
|
||||
public Response getInstalledPlugins() {
|
||||
PluginPermissions.read().check();
|
||||
List<PluginWrapper> plugins = new ArrayList<>(pluginLoader.getInstalledPlugins());
|
||||
return Response.ok(collectionMapper.map(plugins)).build();
|
||||
List<InstalledPlugin> plugins = pluginManager.getInstalled();
|
||||
return Response.ok(collectionMapper.mapInstalled(plugins)).build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,15 +71,11 @@ public class InstalledPluginResource {
|
||||
@Produces(VndMediaType.PLUGIN)
|
||||
public Response getInstalledPlugin(@PathParam("name") String name) {
|
||||
PluginPermissions.read().check();
|
||||
Optional<PluginDto> pluginDto = pluginLoader.getInstalledPlugins()
|
||||
.stream()
|
||||
.filter(plugin -> name.equals(plugin.getPlugin().getInformation().getName()))
|
||||
.map(mapper::map)
|
||||
.findFirst();
|
||||
Optional<InstalledPlugin> pluginDto = pluginManager.getInstalled(name);
|
||||
if (pluginDto.isPresent()) {
|
||||
return Response.ok(pluginDto.get()).build();
|
||||
return Response.ok(mapper.mapInstalled(pluginDto.get())).build();
|
||||
} else {
|
||||
throw notFound(entity(Plugin.class, name));
|
||||
throw notFound(entity(InstalledPluginDescriptor.class, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,12 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@SuppressWarnings("squid:S2160") // we do not need equals for dto
|
||||
public class PluginDto extends HalRepresentation {
|
||||
|
||||
private String name;
|
||||
@@ -18,6 +21,8 @@ public class PluginDto extends HalRepresentation {
|
||||
private String author;
|
||||
private String category;
|
||||
private String avatarUrl;
|
||||
private boolean pending;
|
||||
private Set<String> dependencies;
|
||||
|
||||
public PluginDto(Links links) {
|
||||
add(links);
|
||||
|
||||
@@ -3,11 +3,12 @@ package sonia.scm.api.v2.resources;
|
||||
import com.google.inject.Inject;
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Link;
|
||||
import de.otto.edison.hal.Links;
|
||||
import sonia.scm.plugin.PluginInformation;
|
||||
import sonia.scm.plugin.PluginWrapper;
|
||||
import sonia.scm.plugin.AvailablePlugin;
|
||||
import sonia.scm.plugin.InstalledPlugin;
|
||||
import sonia.scm.plugin.PluginPermissions;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static de.otto.edison.hal.Embedded.embeddedBuilder;
|
||||
@@ -25,14 +26,14 @@ public class PluginDtoCollectionMapper {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
public HalRepresentation map(List<PluginWrapper> plugins) {
|
||||
List<PluginDto> dtos = plugins.stream().map(mapper::map).collect(toList());
|
||||
public HalRepresentation mapInstalled(List<InstalledPlugin> plugins) {
|
||||
List<PluginDto> dtos = plugins.stream().map(mapper::mapInstalled).collect(toList());
|
||||
return new HalRepresentation(createInstalledPluginsLinks(), embedDtos(dtos));
|
||||
}
|
||||
|
||||
public HalRepresentation map(Collection<PluginInformation> plugins) {
|
||||
List<PluginDto> dtos = plugins.stream().map(mapper::map).collect(toList());
|
||||
return new HalRepresentation(createAvailablePluginsLinks(), embedDtos(dtos));
|
||||
public HalRepresentation mapAvailable(List<AvailablePlugin> plugins) {
|
||||
List<PluginDto> dtos = plugins.stream().map(mapper::mapAvailable).collect(toList());
|
||||
return new HalRepresentation(createAvailablePluginsLinks(plugins), embedDtos(dtos));
|
||||
}
|
||||
|
||||
private Links createInstalledPluginsLinks() {
|
||||
@@ -43,14 +44,23 @@ public class PluginDtoCollectionMapper {
|
||||
return linksBuilder.build();
|
||||
}
|
||||
|
||||
private Links createAvailablePluginsLinks() {
|
||||
private Links createAvailablePluginsLinks(List<AvailablePlugin> plugins) {
|
||||
String baseUrl = resourceLinks.availablePluginCollection().self();
|
||||
|
||||
Links.Builder linksBuilder = linkingTo()
|
||||
.with(Links.linkingTo().self(baseUrl).build());
|
||||
|
||||
if (PluginPermissions.manage().isPermitted() && containsPending(plugins)) {
|
||||
linksBuilder.single(Link.link("installPending", resourceLinks.availablePluginCollection().installPending()));
|
||||
}
|
||||
|
||||
return linksBuilder.build();
|
||||
}
|
||||
|
||||
private boolean containsPending(List<AvailablePlugin> plugins) {
|
||||
return plugins.stream().anyMatch(AvailablePlugin::isPending);
|
||||
}
|
||||
|
||||
private Embedded embedDtos(List<PluginDto> dtos) {
|
||||
return embeddedBuilder()
|
||||
.with("plugins", dtos)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.ObjectFactory;
|
||||
import sonia.scm.plugin.AvailablePlugin;
|
||||
import sonia.scm.plugin.InstalledPlugin;
|
||||
import sonia.scm.plugin.Plugin;
|
||||
import sonia.scm.plugin.PluginInformation;
|
||||
import sonia.scm.plugin.PluginState;
|
||||
import sonia.scm.plugin.PluginWrapper;
|
||||
import sonia.scm.plugin.PluginPermissions;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -20,35 +20,50 @@ public abstract class PluginDtoMapper {
|
||||
@Inject
|
||||
private ResourceLinks resourceLinks;
|
||||
|
||||
public PluginDto map(PluginWrapper plugin) {
|
||||
return map(plugin.getPlugin().getInformation());
|
||||
public abstract void map(PluginInformation plugin, @MappingTarget PluginDto dto);
|
||||
|
||||
public PluginDto mapInstalled(InstalledPlugin plugin) {
|
||||
PluginDto dto = createDtoForInstalled(plugin);
|
||||
map(dto, plugin);
|
||||
return dto;
|
||||
}
|
||||
|
||||
public abstract PluginDto map(PluginInformation plugin);
|
||||
public PluginDto mapAvailable(AvailablePlugin plugin) {
|
||||
PluginDto dto = createDtoForAvailable(plugin);
|
||||
map(dto, plugin);
|
||||
dto.setPending(plugin.isPending());
|
||||
return dto;
|
||||
}
|
||||
|
||||
@AfterMapping
|
||||
protected void appendCategory(@MappingTarget PluginDto dto) {
|
||||
private void map(PluginDto dto, Plugin plugin) {
|
||||
dto.setDependencies(plugin.getDescriptor().getDependencies());
|
||||
map(plugin.getDescriptor().getInformation(), dto);
|
||||
if (dto.getCategory() == null) {
|
||||
dto.setCategory("Miscellaneous");
|
||||
}
|
||||
}
|
||||
|
||||
@ObjectFactory
|
||||
public PluginDto createDto(PluginInformation pluginInformation) {
|
||||
Links.Builder linksBuilder;
|
||||
if (pluginInformation.getState() != null && pluginInformation.getState().equals(PluginState.AVAILABLE)) {
|
||||
linksBuilder = linkingTo()
|
||||
.self(resourceLinks.availablePlugin()
|
||||
.self(pluginInformation.getName(), pluginInformation.getVersion()));
|
||||
private PluginDto createDtoForAvailable(AvailablePlugin plugin) {
|
||||
PluginInformation information = plugin.getDescriptor().getInformation();
|
||||
|
||||
linksBuilder.single(link("install", resourceLinks.availablePlugin().install(pluginInformation.getName(), pluginInformation.getVersion())));
|
||||
}
|
||||
else {
|
||||
linksBuilder = linkingTo()
|
||||
.self(resourceLinks.installedPlugin()
|
||||
.self(pluginInformation.getName()));
|
||||
Links.Builder links = linkingTo()
|
||||
.self(resourceLinks.availablePlugin()
|
||||
.self(information.getName()));
|
||||
|
||||
if (!plugin.isPending() && PluginPermissions.manage().isPermitted()) {
|
||||
links.single(link("install", resourceLinks.availablePlugin().install(information.getName())));
|
||||
}
|
||||
|
||||
return new PluginDto(linksBuilder.build());
|
||||
return new PluginDto(links.build());
|
||||
}
|
||||
|
||||
private PluginDto createDtoForInstalled(InstalledPlugin plugin) {
|
||||
PluginInformation information = plugin.getDescriptor().getInformation();
|
||||
|
||||
Links.Builder links = linkingTo()
|
||||
.self(resourceLinks.installedPlugin()
|
||||
.self(information.getName()));
|
||||
|
||||
return new PluginDto(links.build());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import javax.inject.Inject;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
@SuppressWarnings("squid:S1192") // string literals should not be duplicated
|
||||
class ResourceLinks {
|
||||
|
||||
private final ScmPathInfoStore scmPathInfoStore;
|
||||
@@ -694,12 +695,12 @@ class ResourceLinks {
|
||||
availablePluginLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, AvailablePluginResource.class);
|
||||
}
|
||||
|
||||
String self(String name, String version) {
|
||||
return availablePluginLinkBuilder.method("availablePlugins").parameters().method("getAvailablePlugin").parameters(name, version).href();
|
||||
String self(String name) {
|
||||
return availablePluginLinkBuilder.method("availablePlugins").parameters().method("getAvailablePlugin").parameters(name).href();
|
||||
}
|
||||
|
||||
String install(String name, String version) {
|
||||
return availablePluginLinkBuilder.method("availablePlugins").parameters().method("installPlugin").parameters(name, version).href();
|
||||
String install(String name) {
|
||||
return availablePluginLinkBuilder.method("availablePlugins").parameters().method("installPlugin").parameters(name).href();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -714,6 +715,10 @@ class ResourceLinks {
|
||||
availablePluginCollectionLinkBuilder = new LinkBuilder(pathInfo, PluginRootResource.class, AvailablePluginResource.class);
|
||||
}
|
||||
|
||||
String installPending() {
|
||||
return availablePluginCollectionLinkBuilder.method("availablePlugins").parameters().method("installPending").parameters().href();
|
||||
}
|
||||
|
||||
String self() {
|
||||
return availablePluginCollectionLinkBuilder.method("availablePlugins").parameters().method("getAvailablePlugins").parameters().href();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.google.inject.Inject;
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import sonia.scm.plugin.PluginWrapper;
|
||||
import sonia.scm.plugin.InstalledPlugin;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -24,7 +24,7 @@ public class UIPluginDtoCollectionMapper {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
public HalRepresentation map(Collection<PluginWrapper> plugins) {
|
||||
public HalRepresentation map(Collection<InstalledPlugin> plugins) {
|
||||
List<UIPluginDto> dtos = plugins.stream().map(mapper::map).collect(toList());
|
||||
return new HalRepresentation(createLinks(), embedDtos(dtos));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import de.otto.edison.hal.Links;
|
||||
import sonia.scm.plugin.PluginWrapper;
|
||||
import sonia.scm.plugin.InstalledPlugin;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -25,9 +25,9 @@ public class UIPluginDtoMapper {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
public UIPluginDto map(PluginWrapper plugin) {
|
||||
public UIPluginDto map(InstalledPlugin plugin) {
|
||||
UIPluginDto dto = new UIPluginDto(
|
||||
plugin.getPlugin().getInformation().getName(),
|
||||
plugin.getDescriptor().getInformation().getName(),
|
||||
getScriptResources(plugin)
|
||||
);
|
||||
|
||||
@@ -40,8 +40,8 @@ public class UIPluginDtoMapper {
|
||||
return dto;
|
||||
}
|
||||
|
||||
private Set<String> getScriptResources(PluginWrapper wrapper) {
|
||||
Set<String> scriptResources = wrapper.getPlugin().getResources().getScriptResources();
|
||||
private Set<String> getScriptResources(InstalledPlugin wrapper) {
|
||||
Set<String> scriptResources = wrapper.getDescriptor().getResources().getScriptResources();
|
||||
if (scriptResources != null) {
|
||||
return scriptResources.stream()
|
||||
.map(this::addContextPath)
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.plugin.PluginWrapper;
|
||||
import sonia.scm.plugin.InstalledPlugin;
|
||||
import sonia.scm.security.AllowAnonymousAccess;
|
||||
import sonia.scm.web.VndMediaType;
|
||||
|
||||
@@ -46,7 +46,7 @@ public class UIPluginResource {
|
||||
@TypeHint(CollectionDto.class)
|
||||
@Produces(VndMediaType.UI_PLUGIN_COLLECTION)
|
||||
public Response getInstalledPlugins() {
|
||||
List<PluginWrapper> plugins = pluginLoader.getInstalledPlugins()
|
||||
List<InstalledPlugin> plugins = pluginLoader.getInstalledPlugins()
|
||||
.stream()
|
||||
.filter(this::filter)
|
||||
.collect(Collectors.toList());
|
||||
@@ -85,8 +85,8 @@ public class UIPluginResource {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean filter(PluginWrapper plugin) {
|
||||
return plugin.getPlugin().getResources() != null;
|
||||
private boolean filter(InstalledPlugin plugin) {
|
||||
return plugin.getDescriptor().getResources() != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@ import sonia.scm.SCMContext;
|
||||
import sonia.scm.lifecycle.classloading.ClassLoaderLifeCycle;
|
||||
import sonia.scm.migration.UpdateException;
|
||||
import sonia.scm.plugin.DefaultPluginLoader;
|
||||
import sonia.scm.plugin.Plugin;
|
||||
import sonia.scm.plugin.InstalledPluginDescriptor;
|
||||
import sonia.scm.plugin.PluginException;
|
||||
import sonia.scm.plugin.PluginLoadException;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.plugin.PluginWrapper;
|
||||
import sonia.scm.plugin.InstalledPlugin;
|
||||
import sonia.scm.plugin.PluginsInternal;
|
||||
import sonia.scm.plugin.SmpArchive;
|
||||
import sonia.scm.util.IOUtil;
|
||||
@@ -43,7 +43,7 @@ public final class PluginBootstrap {
|
||||
|
||||
private final ClassLoaderLifeCycle classLoaderLifeCycle;
|
||||
private final ServletContext servletContext;
|
||||
private final Set<PluginWrapper> plugins;
|
||||
private final Set<InstalledPlugin> plugins;
|
||||
private final PluginLoader pluginLoader;
|
||||
|
||||
PluginBootstrap(ServletContext servletContext, ClassLoaderLifeCycle classLoaderLifeCycle) {
|
||||
@@ -58,7 +58,7 @@ public final class PluginBootstrap {
|
||||
return pluginLoader;
|
||||
}
|
||||
|
||||
public Set<PluginWrapper> getPlugins() {
|
||||
public Set<InstalledPlugin> getPlugins() {
|
||||
return plugins;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ public final class PluginBootstrap {
|
||||
return new DefaultPluginLoader(servletContext, classLoaderLifeCycle.getBootstrapClassLoader(), plugins);
|
||||
}
|
||||
|
||||
private Set<PluginWrapper> collectPlugins() {
|
||||
private Set<InstalledPlugin> collectPlugins() {
|
||||
try {
|
||||
File pluginDirectory = getPluginDirectory();
|
||||
|
||||
@@ -105,7 +105,7 @@ public final class PluginBootstrap {
|
||||
PluginIndexEntry entry) throws IOException {
|
||||
URL url = context.getResource(PLUGIN_DIRECTORY.concat(entry.getName()));
|
||||
SmpArchive archive = SmpArchive.create(url);
|
||||
Plugin plugin = archive.getPlugin();
|
||||
InstalledPluginDescriptor plugin = archive.getPlugin();
|
||||
|
||||
File directory = PluginsInternal.createPluginDirectory(pluginDirectory, plugin);
|
||||
File checksumFile = PluginsInternal.getChecksumFile(directory);
|
||||
|
||||
@@ -85,7 +85,7 @@ public class DefaultPluginLoader implements PluginLoader
|
||||
* @param installedPlugins
|
||||
*/
|
||||
public DefaultPluginLoader(ServletContext servletContext, ClassLoader parent,
|
||||
Set<PluginWrapper> installedPlugins)
|
||||
Set<InstalledPlugin> installedPlugins)
|
||||
{
|
||||
this.installedPlugins = installedPlugins;
|
||||
this.uberClassLoader = new UberClassLoader(parent, installedPlugins);
|
||||
@@ -95,7 +95,7 @@ public class DefaultPluginLoader implements PluginLoader
|
||||
try
|
||||
{
|
||||
JAXBContext context = JAXBContext.newInstance(ScmModule.class,
|
||||
Plugin.class);
|
||||
InstalledPluginDescriptor.class);
|
||||
|
||||
modules = getInstalled(parent, context, PATH_MODULECONFIG);
|
||||
|
||||
@@ -141,7 +141,7 @@ public class DefaultPluginLoader implements PluginLoader
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Collection<PluginWrapper> getInstalledPlugins()
|
||||
public Collection<InstalledPlugin> getInstalledPlugins()
|
||||
{
|
||||
return installedPlugins;
|
||||
}
|
||||
@@ -178,7 +178,7 @@ public class DefaultPluginLoader implements PluginLoader
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private Iterable<Plugin> unwrap()
|
||||
private Iterable<InstalledPluginDescriptor> unwrap()
|
||||
{
|
||||
return PluginsInternal.unwrap(installedPlugins);
|
||||
}
|
||||
@@ -227,7 +227,7 @@ public class DefaultPluginLoader implements PluginLoader
|
||||
private final ExtensionProcessor extensionProcessor;
|
||||
|
||||
/** Field description */
|
||||
private final Set<PluginWrapper> installedPlugins;
|
||||
private final Set<InstalledPlugin> installedPlugins;
|
||||
|
||||
/** Field description */
|
||||
private final Set<ScmModule> modules;
|
||||
|
||||
@@ -35,685 +35,164 @@ package sonia.scm.plugin;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.github.legman.Subscribe;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.cache.Cache;
|
||||
import sonia.scm.cache.CacheManager;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.config.ScmConfigurationChangedEvent;
|
||||
import sonia.scm.io.ZipUnArchiver;
|
||||
import sonia.scm.util.AssertUtil;
|
||||
import sonia.scm.util.IOUtil;
|
||||
import sonia.scm.util.SystemUtil;
|
||||
import sonia.scm.util.Util;
|
||||
import sonia.scm.version.Version;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.lifecycle.RestartEvent;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.xml.bind.JAXB;
|
||||
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
|
||||
import static sonia.scm.plugin.PluginCenterDtoMapper.*;
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
|
||||
/**
|
||||
* TODO replace aether stuff.
|
||||
* TODO check AdvancedPluginConfiguration from 1.x
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Singleton
|
||||
public class DefaultPluginManager implements PluginManager
|
||||
{
|
||||
public class DefaultPluginManager implements PluginManager {
|
||||
|
||||
/** Field description */
|
||||
public static final String CACHE_NAME = "sonia.cache.plugins";
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultPluginManager.class);
|
||||
|
||||
/** Field description */
|
||||
public static final String ENCODING = "UTF-8";
|
||||
private final ScmEventBus eventBus;
|
||||
private final PluginLoader loader;
|
||||
private final PluginCenter center;
|
||||
private final PluginInstaller installer;
|
||||
private final List<PendingPluginInstallation> pendingQueue = new ArrayList<>();
|
||||
|
||||
/** the logger for DefaultPluginManager */
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(DefaultPluginManager.class);
|
||||
|
||||
/** enable or disable remote plugins */
|
||||
private static final boolean REMOTE_PLUGINS_ENABLED = true;
|
||||
|
||||
/** Field description */
|
||||
public static final Predicate<PluginInformation> FILTER_UPDATES =
|
||||
new StatePluginPredicate(PluginState.UPDATE_AVAILABLE);
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
* @param context
|
||||
* @param configuration
|
||||
* @param pluginLoader
|
||||
* @param cacheManager
|
||||
* @param httpClient
|
||||
*/
|
||||
@Inject
|
||||
public DefaultPluginManager(SCMContextProvider context,
|
||||
ScmConfiguration configuration, PluginLoader pluginLoader,
|
||||
CacheManager cacheManager, AdvancedHttpClient httpClient)
|
||||
{
|
||||
this.context = context;
|
||||
this.configuration = configuration;
|
||||
this.cache = cacheManager.getCache(CACHE_NAME);
|
||||
this.httpClient = httpClient;
|
||||
installedPlugins = new HashMap<>();
|
||||
|
||||
for (PluginWrapper wrapper : pluginLoader.getInstalledPlugins())
|
||||
{
|
||||
Plugin plugin = wrapper.getPlugin();
|
||||
PluginInformation info = plugin.getInformation();
|
||||
|
||||
if ((info != null) && info.isValid())
|
||||
{
|
||||
installedPlugins.put(info.getId(), plugin);
|
||||
}
|
||||
}
|
||||
public DefaultPluginManager(ScmEventBus eventBus, PluginLoader loader, PluginCenter center, PluginInstaller installer) {
|
||||
this.eventBus = eventBus;
|
||||
this.loader = loader;
|
||||
this.center = center;
|
||||
this.installer = installer;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public void clearCache()
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("clear plugin cache");
|
||||
}
|
||||
|
||||
cache.clear();
|
||||
public Optional<AvailablePlugin> getAvailable(String name) {
|
||||
PluginPermissions.read().check();
|
||||
return center.getAvailable()
|
||||
.stream()
|
||||
.filter(filterByName(name))
|
||||
.filter(this::isNotInstalled)
|
||||
.map(p -> getPending(name).orElse(p))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param config
|
||||
*/
|
||||
@Subscribe
|
||||
public void configChanged(ScmConfigurationChangedEvent config)
|
||||
{
|
||||
clearCache();
|
||||
private Optional<AvailablePlugin> getPending(String name) {
|
||||
return pendingQueue
|
||||
.stream()
|
||||
.map(PendingPluginInstallation::getPlugin)
|
||||
.filter(filterByName(name))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
@Override
|
||||
public void install(String id)
|
||||
{
|
||||
public Optional<InstalledPlugin> getInstalled(String name) {
|
||||
PluginPermissions.read().check();
|
||||
return loader.getInstalledPlugins()
|
||||
.stream()
|
||||
.filter(filterByName(name))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InstalledPlugin> getInstalled() {
|
||||
PluginPermissions.read().check();
|
||||
return ImmutableList.copyOf(loader.getInstalledPlugins());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AvailablePlugin> getAvailable() {
|
||||
PluginPermissions.read().check();
|
||||
return center.getAvailable()
|
||||
.stream()
|
||||
.filter(this::isNotInstalled)
|
||||
.map(p -> getPending(p.getDescriptor().getInformation().getName()).orElse(p))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private <T extends Plugin> Predicate<T> filterByName(String name) {
|
||||
return plugin -> name.equals(plugin.getDescriptor().getInformation().getName());
|
||||
}
|
||||
|
||||
private boolean isNotInstalled(AvailablePlugin availablePlugin) {
|
||||
return !getInstalled(availablePlugin.getDescriptor().getInformation().getName()).isPresent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void install(String name, boolean restartAfterInstallation) {
|
||||
PluginPermissions.manage().check();
|
||||
|
||||
PluginCenter center = getPluginCenter();
|
||||
List<AvailablePlugin> plugins = collectPluginsToInstall(name);
|
||||
List<PendingPluginInstallation> pendingInstallations = new ArrayList<>();
|
||||
for (AvailablePlugin plugin : plugins) {
|
||||
try {
|
||||
PendingPluginInstallation pending = installer.install(plugin);
|
||||
pendingInstallations.add(pending);
|
||||
} catch (PluginInstallException ex) {
|
||||
cancelPending(pendingInstallations);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
for (PluginInformation plugin : center.getPlugins())
|
||||
{
|
||||
String pluginId = plugin.getId();
|
||||
|
||||
if (Util.isNotEmpty(pluginId) && pluginId.equals(id))
|
||||
{
|
||||
plugin.setState(PluginState.INSTALLED);
|
||||
|
||||
// ugly workaround
|
||||
Plugin newPlugin = new Plugin();
|
||||
|
||||
// TODO check
|
||||
// newPlugin.setInformation(plugin);
|
||||
installedPlugins.put(id, newPlugin);
|
||||
if (!pendingInstallations.isEmpty()) {
|
||||
if (restartAfterInstallation) {
|
||||
restart("plugin installation");
|
||||
} else {
|
||||
pendingQueue.addAll(pendingInstallations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param packageStream
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public void installPackage(InputStream packageStream) throws IOException
|
||||
{
|
||||
public void installPendingAndRestart() {
|
||||
PluginPermissions.manage().check();
|
||||
|
||||
File tempDirectory = Files.createTempDir();
|
||||
|
||||
try
|
||||
{
|
||||
new ZipUnArchiver().extractArchive(packageStream, tempDirectory);
|
||||
|
||||
Plugin plugin = JAXB.unmarshal(new File(tempDirectory, "plugin.xml"),
|
||||
Plugin.class);
|
||||
|
||||
PluginCondition condition = plugin.getCondition();
|
||||
|
||||
if ((condition != null) &&!condition.isSupported())
|
||||
{
|
||||
throw new PluginConditionFailedException(condition);
|
||||
}
|
||||
|
||||
/*
|
||||
* AetherPluginHandler aph = new AetherPluginHandler(this, context,
|
||||
* configuration);
|
||||
* Collection<PluginRepository> repositories =
|
||||
* Sets.newHashSet(new PluginRepository("package-repository",
|
||||
* "file://".concat(tempDirectory.getAbsolutePath())));
|
||||
*
|
||||
* aph.setPluginRepositories(repositories);
|
||||
*
|
||||
* aph.install(plugin.getInformation().getId());
|
||||
*/
|
||||
plugin.getInformation().setState(PluginState.INSTALLED);
|
||||
installedPlugins.put(plugin.getInformation().getId(), plugin);
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
IOUtil.delete(tempDirectory);
|
||||
if (!pendingQueue.isEmpty()) {
|
||||
restart("install pending plugins");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
@Override
|
||||
public void uninstall(String id)
|
||||
{
|
||||
PluginPermissions.manage().check();
|
||||
private void restart(String cause) {
|
||||
eventBus.post(new RestartEvent(PluginManager.class, cause));
|
||||
}
|
||||
|
||||
Plugin plugin = installedPlugins.get(id);
|
||||
private void cancelPending(List<PendingPluginInstallation> pendingInstallations) {
|
||||
pendingInstallations.forEach(PendingPluginInstallation::cancel);
|
||||
}
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
String pluginPrefix = getPluginIdPrefix(id);
|
||||
private List<AvailablePlugin> collectPluginsToInstall(String name) {
|
||||
List<AvailablePlugin> plugins = new ArrayList<>();
|
||||
collectPluginsToInstall(plugins, name);
|
||||
return plugins;
|
||||
}
|
||||
|
||||
for (String nid : installedPlugins.keySet())
|
||||
{
|
||||
if (nid.startsWith(pluginPrefix))
|
||||
{
|
||||
id = nid;
|
||||
plugin = installedPlugins.get(nid);
|
||||
private boolean isInstalledOrPending(String name) {
|
||||
return getInstalled(name).isPresent() || getPending(name).isPresent();
|
||||
}
|
||||
|
||||
break;
|
||||
private void collectPluginsToInstall(List<AvailablePlugin> plugins, String name) {
|
||||
if (!isInstalledOrPending(name)) {
|
||||
AvailablePlugin plugin = getAvailable(name).orElseThrow(() -> NotFoundException.notFound(entity(AvailablePlugin.class, name)));
|
||||
|
||||
Set<String> dependencies = plugin.getDescriptor().getDependencies();
|
||||
if (dependencies != null) {
|
||||
for (String dependency: dependencies){
|
||||
collectPluginsToInstall(plugins, dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin == null)
|
||||
{
|
||||
throw new PluginNotInstalledException(id.concat(" is not install"));
|
||||
}
|
||||
|
||||
/*
|
||||
* if (pluginHandler == null)
|
||||
* {
|
||||
* getPluginCenter();
|
||||
* }
|
||||
*
|
||||
* pluginHandler.uninstall(id);
|
||||
*/
|
||||
installedPlugins.remove(id);
|
||||
preparePlugins(getPluginCenter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param id
|
||||
*/
|
||||
@Override
|
||||
public void update(String id)
|
||||
{
|
||||
PluginPermissions.manage().check();
|
||||
|
||||
String[] idParts = id.split(":");
|
||||
String name = idParts[0];
|
||||
PluginInformation installed = null;
|
||||
|
||||
for (PluginInformation info : getInstalled())
|
||||
{
|
||||
if (name.equals(info.getName()))
|
||||
{
|
||||
installed = info;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (installed == null)
|
||||
{
|
||||
StringBuilder msg = new StringBuilder(name);
|
||||
|
||||
msg.append(" is not install");
|
||||
|
||||
throw new PluginNotInstalledException(msg.toString());
|
||||
}
|
||||
|
||||
uninstall(installed.getId());
|
||||
install(id);
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param id
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public PluginInformation get(String id)
|
||||
{
|
||||
PluginPermissions.read().check();
|
||||
|
||||
PluginInformation result = null;
|
||||
|
||||
for (PluginInformation info : getPluginCenter().getPlugins())
|
||||
{
|
||||
if (id.equals(info.getId()))
|
||||
{
|
||||
result = info;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param predicate
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Set<PluginInformation> get(Predicate<PluginInformation> predicate)
|
||||
{
|
||||
AssertUtil.assertIsNotNull(predicate);
|
||||
PluginPermissions.read().check();
|
||||
|
||||
Set<PluginInformation> infoSet = new HashSet<>();
|
||||
|
||||
filter(infoSet, getInstalled(), predicate);
|
||||
filter(infoSet, getPluginCenter().getPlugins(), predicate);
|
||||
|
||||
return infoSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Collection<PluginInformation> getAll()
|
||||
{
|
||||
PluginPermissions.read().check();
|
||||
|
||||
Set<PluginInformation> infoSet = getInstalled();
|
||||
|
||||
infoSet.addAll(getPluginCenter().getPlugins());
|
||||
|
||||
return infoSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Collection<PluginInformation> getAvailable()
|
||||
{
|
||||
PluginPermissions.read().check();
|
||||
|
||||
Set<PluginInformation> availablePlugins = new HashSet<>();
|
||||
Set<PluginInformation> centerPlugins = getPluginCenter().getPlugins();
|
||||
|
||||
for (PluginInformation info : centerPlugins)
|
||||
{
|
||||
if (!installedPlugins.containsKey(info.getName()))
|
||||
{
|
||||
availablePlugins.add(info);
|
||||
}
|
||||
}
|
||||
|
||||
return availablePlugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Set<PluginInformation> getAvailableUpdates()
|
||||
{
|
||||
PluginPermissions.read().check();
|
||||
|
||||
return get(FILTER_UPDATES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Set<PluginInformation> getInstalled()
|
||||
{
|
||||
PluginPermissions.read().check();
|
||||
|
||||
Set<PluginInformation> infoSet = new LinkedHashSet<>();
|
||||
|
||||
for (Plugin plugin : installedPlugins.values())
|
||||
{
|
||||
infoSet.add(plugin.getInformation());
|
||||
}
|
||||
|
||||
return infoSet;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param url
|
||||
* @return
|
||||
*/
|
||||
private String buildPluginUrl(String url)
|
||||
{
|
||||
String os = SystemUtil.getOS();
|
||||
String arch = SystemUtil.getArch();
|
||||
|
||||
try
|
||||
{
|
||||
os = URLEncoder.encode(os, ENCODING);
|
||||
}
|
||||
catch (UnsupportedEncodingException ex)
|
||||
{
|
||||
logger.error(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
return url.replace("{version}", context.getVersion()).replace("{os}",
|
||||
os).replace("{arch}", arch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param target
|
||||
* @param source
|
||||
* @param predicate
|
||||
*/
|
||||
private void filter(Set<PluginInformation> target,
|
||||
Collection<PluginInformation> source,
|
||||
Predicate<PluginInformation> predicate)
|
||||
{
|
||||
for (PluginInformation info : source)
|
||||
{
|
||||
if (predicate.apply(info))
|
||||
{
|
||||
target.add(info);
|
||||
}
|
||||
plugins.add(plugin);
|
||||
} else {
|
||||
LOG.info("plugin {} is already installed or installation is pending, skipping installation", name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param available
|
||||
*/
|
||||
private void preparePlugin(PluginInformation available)
|
||||
{
|
||||
PluginState state = PluginState.AVAILABLE;
|
||||
|
||||
for (PluginInformation installed : getInstalled())
|
||||
{
|
||||
if (isSamePlugin(available, installed))
|
||||
{
|
||||
if (installed.getVersion().equals(available.getVersion()))
|
||||
{
|
||||
state = PluginState.INSTALLED;
|
||||
}
|
||||
else if (isNewer(available, installed))
|
||||
{
|
||||
state = PluginState.UPDATE_AVAILABLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = PluginState.NEWER_VERSION_INSTALLED;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
available.setState(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param pc
|
||||
*/
|
||||
private void preparePlugins(PluginCenter pc)
|
||||
{
|
||||
Set<PluginInformation> infoSet = pc.getPlugins();
|
||||
|
||||
if (infoSet != null)
|
||||
{
|
||||
Iterator<PluginInformation> pit = infoSet.iterator();
|
||||
|
||||
while (pit.hasNext())
|
||||
{
|
||||
PluginInformation available = pit.next();
|
||||
|
||||
if (isCorePluging(available))
|
||||
{
|
||||
pit.remove();
|
||||
}
|
||||
else
|
||||
{
|
||||
preparePlugin(available);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private PluginCenter getPluginCenter()
|
||||
{
|
||||
PluginCenter center = cache.get(PluginCenter.class.getName());
|
||||
|
||||
if (center == null)
|
||||
{
|
||||
synchronized (DefaultPluginManager.class)
|
||||
{
|
||||
String pluginUrl = buildPluginUrl(configuration.getPluginUrl());
|
||||
logger.info("fetch plugin information from {}", pluginUrl);
|
||||
|
||||
if (REMOTE_PLUGINS_ENABLED && Util.isNotEmpty(pluginUrl))
|
||||
{
|
||||
try
|
||||
{
|
||||
center = new PluginCenter();
|
||||
PluginCenterDto pluginCenterDto = httpClient.get(pluginUrl).request().contentFromJson(PluginCenterDto.class);
|
||||
Set<PluginInformation> pluginInformationSet = map(pluginCenterDto.getEmbedded().getPlugins());
|
||||
center.setPlugins(pluginInformationSet);
|
||||
preparePlugins(center);
|
||||
cache.put(PluginCenter.class.getName(), center);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
logger.error("could not load plugins from plugin center", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(center == null) {
|
||||
center = new PluginCenter();
|
||||
}
|
||||
}
|
||||
|
||||
return center;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param pluginId
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private String getPluginIdPrefix(String pluginId)
|
||||
{
|
||||
return pluginId.substring(0, pluginId.lastIndexOf(':'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param available
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private boolean isCorePluging(PluginInformation available)
|
||||
{
|
||||
boolean core = false;
|
||||
|
||||
for (Plugin installedPlugin : installedPlugins.values())
|
||||
{
|
||||
PluginInformation installed = installedPlugin.getInformation();
|
||||
|
||||
if (isSamePlugin(available, installed)
|
||||
&& (installed.getState() == PluginState.CORE))
|
||||
{
|
||||
core = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return core;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param available
|
||||
* @param installed
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private boolean isNewer(PluginInformation available,
|
||||
PluginInformation installed)
|
||||
{
|
||||
boolean result = false;
|
||||
Version version = Version.parse(available.getVersion());
|
||||
|
||||
if (version != null)
|
||||
{
|
||||
result = version.isNewer(installed.getVersion());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param p1
|
||||
* @param p2
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private boolean isSamePlugin(PluginInformation p1, PluginInformation p2)
|
||||
{
|
||||
return p1.getName().equals(p2.getName());
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private final Cache<String, PluginCenter> cache;
|
||||
|
||||
/** Field description */
|
||||
private final AdvancedHttpClient httpClient;
|
||||
|
||||
/** Field description */
|
||||
private final ScmConfiguration configuration;
|
||||
|
||||
/** Field description */
|
||||
private final SCMContextProvider context;
|
||||
|
||||
/** Field description */
|
||||
private final Map<String, Plugin> installedPlugins;
|
||||
}
|
||||
|
||||
@@ -71,11 +71,11 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
public DefaultUberWebResourceLoader(ServletContext servletContext, Iterable<PluginWrapper> plugins) {
|
||||
public DefaultUberWebResourceLoader(ServletContext servletContext, Iterable<InstalledPlugin> plugins) {
|
||||
this(servletContext, plugins, SCMContext.getContext().getStage());
|
||||
}
|
||||
|
||||
public DefaultUberWebResourceLoader(ServletContext servletContext, Iterable<PluginWrapper> plugins, Stage stage) {
|
||||
public DefaultUberWebResourceLoader(ServletContext servletContext, Iterable<InstalledPlugin> plugins, Stage stage) {
|
||||
this.servletContext = servletContext;
|
||||
this.plugins = plugins;
|
||||
this.cache = createCache(stage);
|
||||
@@ -153,7 +153,7 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
|
||||
resources.add(ctxResource);
|
||||
}
|
||||
|
||||
for (PluginWrapper wrapper : plugins)
|
||||
for (InstalledPlugin wrapper : plugins)
|
||||
{
|
||||
URL resource = nonDirectory(wrapper.getWebResourceLoader().getResource(path));
|
||||
|
||||
@@ -205,7 +205,7 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
|
||||
|
||||
if (resource == null)
|
||||
{
|
||||
for (PluginWrapper wrapper : plugins)
|
||||
for (InstalledPlugin wrapper : plugins)
|
||||
{
|
||||
resource = nonDirectory(wrapper.getWebResourceLoader().getResource(path));
|
||||
|
||||
@@ -259,7 +259,7 @@ public class DefaultUberWebResourceLoader implements UberWebResourceLoader
|
||||
private final Cache<String, URL> cache;
|
||||
|
||||
/** Field description */
|
||||
private final Iterable<PluginWrapper> plugins;
|
||||
private final Iterable<InstalledPlugin> plugins;
|
||||
|
||||
/** Field description */
|
||||
private final ServletContext servletContext;
|
||||
|
||||
@@ -63,7 +63,7 @@ public final class ExplodedSmp implements Comparable<ExplodedSmp>
|
||||
* @param path
|
||||
* @param plugin
|
||||
*/
|
||||
ExplodedSmp(Path path, Plugin plugin)
|
||||
ExplodedSmp(Path path, InstalledPluginDescriptor plugin)
|
||||
{
|
||||
logger.trace("create exploded scm for plugin {} and dependencies {}", plugin.getInformation().getName(), plugin.getDependencies());
|
||||
this.path = path;
|
||||
@@ -163,7 +163,7 @@ public final class ExplodedSmp implements Comparable<ExplodedSmp>
|
||||
*
|
||||
* @return plugin descriptor
|
||||
*/
|
||||
public Plugin getPlugin()
|
||||
public InstalledPluginDescriptor getPlugin()
|
||||
{
|
||||
return plugin;
|
||||
}
|
||||
@@ -202,5 +202,5 @@ public final class ExplodedSmp implements Comparable<ExplodedSmp>
|
||||
private final Path path;
|
||||
|
||||
/** plugin object */
|
||||
private final Plugin plugin;
|
||||
private final InstalledPluginDescriptor plugin;
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class OverviewPluginPredicate implements Predicate<PluginInformation>
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
public static final OverviewPluginPredicate INSTANCE =
|
||||
new OverviewPluginPredicate();
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param plugin
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean apply(PluginInformation plugin)
|
||||
{
|
||||
return plugin.getState() != PluginState.NEWER_VERSION_INSTALLED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
class PendingPluginInstallation {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PendingPluginInstallation.class);
|
||||
|
||||
private final AvailablePlugin plugin;
|
||||
private final Path file;
|
||||
|
||||
PendingPluginInstallation(AvailablePlugin plugin, Path file) {
|
||||
this.plugin = plugin;
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public AvailablePlugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
String name = plugin.getDescriptor().getInformation().getName();
|
||||
LOG.info("cancel installation of plugin {}", name);
|
||||
try {
|
||||
Files.delete(file);
|
||||
} catch (IOException ex) {
|
||||
throw new PluginFailedToCancelInstallationException("failed to cancel installation of plugin " + name, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
55
scm-webapp/src/main/java/sonia/scm/plugin/PluginCenter.java
Normal file
55
scm-webapp/src/main/java/sonia/scm/plugin/PluginCenter.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.cache.Cache;
|
||||
import sonia.scm.cache.CacheManager;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
import sonia.scm.util.SystemUtil;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.Set;
|
||||
|
||||
public class PluginCenter {
|
||||
|
||||
private static final String CACHE_NAME = "sonia.cache.plugins";
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PluginCenter.class);
|
||||
|
||||
private final SCMContextProvider context;
|
||||
private final ScmConfiguration configuration;
|
||||
private final PluginCenterLoader loader;
|
||||
private final Cache<String, Set<AvailablePlugin>> cache;
|
||||
|
||||
@Inject
|
||||
public PluginCenter(SCMContextProvider context, CacheManager cacheManager, ScmConfiguration configuration, PluginCenterLoader loader) {
|
||||
this.context = context;
|
||||
this.configuration = configuration;
|
||||
this.loader = loader;
|
||||
this.cache = cacheManager.getCache(CACHE_NAME);
|
||||
}
|
||||
|
||||
synchronized Set<AvailablePlugin> getAvailable() {
|
||||
String url = buildPluginUrl(configuration.getPluginUrl());
|
||||
Set<AvailablePlugin> plugins = cache.get(url);
|
||||
if (plugins == null) {
|
||||
LOG.debug("no cached available plugins found, start fetching");
|
||||
plugins = loader.load(url);
|
||||
cache.put(url, plugins);
|
||||
} else {
|
||||
LOG.debug("return available plugins from cache");
|
||||
}
|
||||
return plugins;
|
||||
}
|
||||
|
||||
private String buildPluginUrl(String url) {
|
||||
String os = HttpUtil.encode(SystemUtil.getOS());
|
||||
String arch = SystemUtil.getArch();
|
||||
return url.replace("{version}", context.getVersion())
|
||||
.replace("{os}", os)
|
||||
.replace("{arch}", arch);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package sonia.scm.plugin;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
@@ -11,6 +12,7 @@ import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@XmlRootElement
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@@ -56,8 +58,8 @@ public final class PluginCenterDto implements Serializable {
|
||||
@XmlElement(name = "conditions")
|
||||
private Condition conditions;
|
||||
|
||||
@XmlElement(name = "dependecies")
|
||||
private Dependency dependencies;
|
||||
@XmlElement(name = "dependencies")
|
||||
private Set<String> dependencies;
|
||||
|
||||
@XmlElement(name = "_links")
|
||||
private Map<String, Link> links;
|
||||
@@ -75,15 +77,9 @@ public final class PluginCenterDto implements Serializable {
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlRootElement(name = "dependencies")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
static class Dependency {
|
||||
private String name;
|
||||
}
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@Getter
|
||||
static class Link {
|
||||
private String href;
|
||||
}
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Mapper
|
||||
public interface PluginCenterDtoMapper {
|
||||
public abstract class PluginCenterDtoMapper {
|
||||
|
||||
@Mapping(source = "conditions", target = "condition")
|
||||
PluginInformation map(PluginCenterDto.Plugin plugin);
|
||||
static final PluginCenterDtoMapper INSTANCE = Mappers.getMapper(PluginCenterDtoMapper.class);
|
||||
|
||||
PluginCondition map(PluginCenterDto.Condition condition);
|
||||
abstract PluginInformation map(PluginCenterDto.Plugin plugin);
|
||||
abstract PluginCondition map(PluginCenterDto.Condition condition);
|
||||
|
||||
static Set<PluginInformation> map(List<PluginCenterDto.Plugin> dtos) {
|
||||
PluginCenterDtoMapper mapper = Mappers.getMapper(PluginCenterDtoMapper.class);
|
||||
Set<PluginInformation> plugins = new HashSet<>();
|
||||
for (PluginCenterDto.Plugin plugin : dtos) {
|
||||
plugins.add(mapper.map(plugin));
|
||||
Set<AvailablePlugin> map(PluginCenterDto pluginCenterDto) {
|
||||
Set<AvailablePlugin> plugins = new HashSet<>();
|
||||
for (PluginCenterDto.Plugin plugin : pluginCenterDto.getEmbedded().getPlugins()) {
|
||||
String url = plugin.getLinks().get("download").getHref();
|
||||
AvailablePluginDescriptor descriptor = new AvailablePluginDescriptor(
|
||||
map(plugin), map(plugin.getConditions()), plugin.getDependencies(), url, plugin.getSha256()
|
||||
);
|
||||
plugins.add(new AvailablePlugin(descriptor));
|
||||
}
|
||||
return plugins;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
class PluginCenterLoader {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PluginCenterLoader.class);
|
||||
|
||||
private final AdvancedHttpClient client;
|
||||
private final PluginCenterDtoMapper mapper;
|
||||
|
||||
@Inject
|
||||
public PluginCenterLoader(AdvancedHttpClient client) {
|
||||
this(client, PluginCenterDtoMapper.INSTANCE);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
PluginCenterLoader(AdvancedHttpClient client, PluginCenterDtoMapper mapper) {
|
||||
this.client = client;
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
Set<AvailablePlugin> load(String url) {
|
||||
try {
|
||||
LOG.info("fetch plugins from {}", url);
|
||||
PluginCenterDto pluginCenterDto = client.get(url).request().contentFromJson(PluginCenterDto.class);
|
||||
return mapper.map(pluginCenterDto);
|
||||
} catch (IOException ex) {
|
||||
LOG.error("failed to load plugins from plugin center, returning empty list");
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
public class PluginChecksumMismatchException extends PluginInstallException {
|
||||
public PluginChecksumMismatchException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
public class PluginDownloadException extends PluginInstallException {
|
||||
public PluginDownloadException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
public class PluginFailedToCancelInstallationException extends RuntimeException {
|
||||
public PluginFailedToCancelInstallationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
public class PluginInstallException extends RuntimeException {
|
||||
|
||||
public PluginInstallException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PluginInstallException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
import com.google.common.hash.Hashing;
|
||||
import com.google.common.hash.HashingInputStream;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage") // guava hash is marked as unstable
|
||||
class PluginInstaller {
|
||||
|
||||
private final SCMContextProvider context;
|
||||
private final AdvancedHttpClient client;
|
||||
|
||||
@Inject
|
||||
public PluginInstaller(SCMContextProvider context, AdvancedHttpClient client) {
|
||||
this.context = context;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@SuppressWarnings("squid:S4790") // hashing should be safe
|
||||
public PendingPluginInstallation install(AvailablePlugin plugin) {
|
||||
Path file = null;
|
||||
try (HashingInputStream input = new HashingInputStream(Hashing.sha256(), download(plugin))) {
|
||||
file = createFile(plugin);
|
||||
Files.copy(input, file);
|
||||
|
||||
verifyChecksum(plugin, input.hash(), file);
|
||||
return new PendingPluginInstallation(plugin.install(), file);
|
||||
} catch (IOException ex) {
|
||||
cleanup(file);
|
||||
throw new PluginDownloadException("failed to download plugin", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanup(Path file) {
|
||||
try {
|
||||
if (file != null) {
|
||||
Files.deleteIfExists(file);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new PluginInstallException("failed to cleanup, after broken installation");
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyChecksum(AvailablePlugin plugin, HashCode hash, Path file) {
|
||||
Optional<String> checksum = plugin.getDescriptor().getChecksum();
|
||||
if (checksum.isPresent()) {
|
||||
String calculatedChecksum = hash.toString();
|
||||
if (!checksum.get().equalsIgnoreCase(calculatedChecksum)) {
|
||||
cleanup(file);
|
||||
throw new PluginChecksumMismatchException(
|
||||
String.format("downloaded plugin checksum %s does not match expected %s", calculatedChecksum, checksum.get())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream download(AvailablePlugin plugin) throws IOException {
|
||||
return client.get(plugin.getDescriptor().getUrl()).request().contentAsStream();
|
||||
}
|
||||
|
||||
private Path createFile(AvailablePlugin plugin) throws IOException {
|
||||
Path directory = context.resolve(Paths.get("plugins"));
|
||||
Files.createDirectories(directory);
|
||||
return directory.resolve(plugin.getDescriptor().getInformation().getName() + ".smp");
|
||||
}
|
||||
}
|
||||
@@ -157,7 +157,7 @@ public final class PluginNode
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public PluginWrapper getWrapper()
|
||||
public InstalledPlugin getWrapper()
|
||||
{
|
||||
return wrapper;
|
||||
}
|
||||
@@ -170,7 +170,7 @@ public final class PluginNode
|
||||
*
|
||||
* @param wrapper
|
||||
*/
|
||||
public void setWrapper(PluginWrapper wrapper)
|
||||
public void setWrapper(InstalledPlugin wrapper)
|
||||
{
|
||||
this.wrapper = wrapper;
|
||||
}
|
||||
@@ -192,5 +192,5 @@ public final class PluginNode
|
||||
private final ExplodedSmp plugin;
|
||||
|
||||
/** Field description */
|
||||
private PluginWrapper wrapper;
|
||||
private InstalledPlugin wrapper;
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ public final class PluginProcessor
|
||||
|
||||
try
|
||||
{
|
||||
this.context = JAXBContext.newInstance(Plugin.class);
|
||||
this.context = JAXBContext.newInstance(InstalledPluginDescriptor.class);
|
||||
}
|
||||
catch (JAXBException ex)
|
||||
{
|
||||
@@ -160,7 +160,7 @@ public final class PluginProcessor
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public Set<PluginWrapper> collectPlugins(ClassLoader classLoader)
|
||||
public Set<InstalledPlugin> collectPlugins(ClassLoader classLoader)
|
||||
throws IOException
|
||||
{
|
||||
logger.info("collect plugins");
|
||||
@@ -187,7 +187,7 @@ public final class PluginProcessor
|
||||
|
||||
logger.trace("create plugin wrappers and build classloaders");
|
||||
|
||||
Set<PluginWrapper> wrappers = createPluginWrappers(classLoader, rootNodes);
|
||||
Set<InstalledPlugin> wrappers = createPluginWrappers(classLoader, rootNodes);
|
||||
|
||||
logger.debug("collected {} plugins", wrappers.size());
|
||||
|
||||
@@ -204,7 +204,7 @@ public final class PluginProcessor
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void appendPluginWrapper(Set<PluginWrapper> plugins,
|
||||
private void appendPluginWrapper(Set<InstalledPlugin> plugins,
|
||||
ClassLoader classLoader, PluginNode node)
|
||||
throws IOException
|
||||
{
|
||||
@@ -217,7 +217,7 @@ public final class PluginProcessor
|
||||
|
||||
for (PluginNode parent : node.getParents())
|
||||
{
|
||||
PluginWrapper wrapper = parent.getWrapper();
|
||||
InstalledPlugin wrapper = parent.getWrapper();
|
||||
|
||||
if (wrapper != null)
|
||||
{
|
||||
@@ -236,8 +236,8 @@ public final class PluginProcessor
|
||||
|
||||
}
|
||||
|
||||
PluginWrapper plugin =
|
||||
createPluginWrapper(createParentPluginClassLoader(classLoader, parents),
|
||||
InstalledPlugin plugin =
|
||||
createPlugin(createParentPluginClassLoader(classLoader, parents),
|
||||
smp);
|
||||
|
||||
if (plugin != null)
|
||||
@@ -257,7 +257,7 @@ public final class PluginProcessor
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private void appendPluginWrappers(Set<PluginWrapper> plugins,
|
||||
private void appendPluginWrappers(Set<InstalledPlugin> plugins,
|
||||
ClassLoader classLoader, List<PluginNode> nodes)
|
||||
throws IOException
|
||||
{
|
||||
@@ -371,7 +371,7 @@ public final class PluginProcessor
|
||||
|
||||
ClassLoader classLoader;
|
||||
URL[] urlArray = urls.toArray(new URL[urls.size()]);
|
||||
Plugin plugin = smp.getPlugin();
|
||||
InstalledPluginDescriptor plugin = smp.getPlugin();
|
||||
|
||||
String id = plugin.getInformation().getName(false);
|
||||
|
||||
@@ -431,73 +431,36 @@ public final class PluginProcessor
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param classLoader
|
||||
* @param descriptor
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private Plugin createPlugin(ClassLoader classLoader, Path descriptor)
|
||||
{
|
||||
private InstalledPluginDescriptor createDescriptor(ClassLoader classLoader, Path descriptor) {
|
||||
ClassLoader ctxcl = Thread.currentThread().getContextClassLoader();
|
||||
|
||||
Thread.currentThread().setContextClassLoader(classLoader);
|
||||
|
||||
try
|
||||
{
|
||||
return (Plugin) context.createUnmarshaller().unmarshal(
|
||||
descriptor.toFile());
|
||||
}
|
||||
catch (JAXBException ex)
|
||||
{
|
||||
throw new PluginLoadException(
|
||||
"could not load plugin desriptor ".concat(descriptor.toString()), ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try {
|
||||
return (InstalledPluginDescriptor) context.createUnmarshaller().unmarshal(descriptor.toFile());
|
||||
} catch (JAXBException ex) {
|
||||
throw new PluginLoadException("could not load plugin desriptor ".concat(descriptor.toString()), ex);
|
||||
} finally {
|
||||
Thread.currentThread().setContextClassLoader(ctxcl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param classLoader
|
||||
* @param smp
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private PluginWrapper createPluginWrapper(ClassLoader classLoader,
|
||||
ExplodedSmp smp)
|
||||
throws IOException
|
||||
{
|
||||
PluginWrapper wrapper = null;
|
||||
private InstalledPlugin createPlugin(ClassLoader classLoader, ExplodedSmp smp) throws IOException {
|
||||
InstalledPlugin plugin = null;
|
||||
Path directory = smp.getPath();
|
||||
Path descriptor = directory.resolve(PluginConstants.FILE_DESCRIPTOR);
|
||||
Path descriptorPath = directory.resolve(PluginConstants.FILE_DESCRIPTOR);
|
||||
|
||||
if (Files.exists(descriptor))
|
||||
{
|
||||
if (Files.exists(descriptorPath)) {
|
||||
ClassLoader cl = createClassLoader(classLoader, smp);
|
||||
|
||||
Plugin plugin = createPlugin(cl, descriptor);
|
||||
InstalledPluginDescriptor descriptor = createDescriptor(cl, descriptorPath);
|
||||
|
||||
WebResourceLoader resourceLoader = createWebResourceLoader(directory);
|
||||
|
||||
wrapper = new PluginWrapper(plugin, cl, resourceLoader, directory);
|
||||
}
|
||||
else
|
||||
{
|
||||
plugin = new InstalledPlugin(descriptor, cl, resourceLoader, directory);
|
||||
} else {
|
||||
logger.warn("found plugin directory without plugin descriptor");
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
return plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -512,11 +475,11 @@ public final class PluginProcessor
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private Set<PluginWrapper> createPluginWrappers(ClassLoader classLoader,
|
||||
List<PluginNode> rootNodes)
|
||||
private Set<InstalledPlugin> createPluginWrappers(ClassLoader classLoader,
|
||||
List<PluginNode> rootNodes)
|
||||
throws IOException
|
||||
{
|
||||
Set<PluginWrapper> plugins = Sets.newHashSet();
|
||||
Set<InstalledPlugin> plugins = Sets.newHashSet();
|
||||
|
||||
appendPluginWrappers(plugins, classLoader, rootNodes);
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ public final class PluginTree
|
||||
|
||||
for (ExplodedSmp smp : smpOrdered)
|
||||
{
|
||||
Plugin plugin = smp.getPlugin();
|
||||
InstalledPluginDescriptor plugin = smp.getPlugin();
|
||||
|
||||
if (plugin.getScmVersion() != SCM_VERSION)
|
||||
{
|
||||
|
||||
@@ -87,8 +87,8 @@ public final class PluginsInternal
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public static Set<PluginWrapper> collectPlugins(ClassLoaderLifeCycle classLoaderLifeCycle,
|
||||
Path directory)
|
||||
public static Set<InstalledPlugin> collectPlugins(ClassLoaderLifeCycle classLoaderLifeCycle,
|
||||
Path directory)
|
||||
throws IOException
|
||||
{
|
||||
PluginProcessor processor = new PluginProcessor(classLoaderLifeCycle, directory);
|
||||
@@ -105,7 +105,7 @@ public final class PluginsInternal
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static File createPluginDirectory(File parent, Plugin plugin)
|
||||
public static File createPluginDirectory(File parent, InstalledPluginDescriptor plugin)
|
||||
{
|
||||
PluginInformation info = plugin.getInformation();
|
||||
|
||||
@@ -159,7 +159,7 @@ public final class PluginsInternal
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Iterable<Plugin> unwrap(Iterable<PluginWrapper> wrapped)
|
||||
public static Iterable<InstalledPluginDescriptor> unwrap(Iterable<InstalledPlugin> wrapped)
|
||||
{
|
||||
return Iterables.transform(wrapped, new Unwrap());
|
||||
}
|
||||
@@ -188,7 +188,7 @@ public final class PluginsInternal
|
||||
* @version Enter version here..., 14/06/05
|
||||
* @author Enter your name here...
|
||||
*/
|
||||
private static class Unwrap implements Function<PluginWrapper, Plugin>
|
||||
private static class Unwrap implements Function<InstalledPlugin, InstalledPluginDescriptor>
|
||||
{
|
||||
|
||||
/**
|
||||
@@ -200,9 +200,9 @@ public final class PluginsInternal
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Plugin apply(PluginWrapper wrapper)
|
||||
public InstalledPluginDescriptor apply(InstalledPlugin wrapper)
|
||||
{
|
||||
return wrapper.getPlugin();
|
||||
return wrapper.getDescriptor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public final class UberClassLoader extends ClassLoader
|
||||
* @param parent
|
||||
* @param plugins
|
||||
*/
|
||||
public UberClassLoader(ClassLoader parent, Iterable<PluginWrapper> plugins)
|
||||
public UberClassLoader(ClassLoader parent, Iterable<InstalledPlugin> plugins)
|
||||
{
|
||||
super(parent);
|
||||
this.plugins = plugins;
|
||||
@@ -87,7 +87,7 @@ public final class UberClassLoader extends ClassLoader
|
||||
}
|
||||
|
||||
private Class<?> findClassInPlugins(String name) throws ClassNotFoundException {
|
||||
for (PluginWrapper plugin : plugins) {
|
||||
for (InstalledPlugin plugin : plugins) {
|
||||
Class<?> clazz = findClass(plugin.getClassLoader(), name);
|
||||
if (clazz != null) {
|
||||
return clazz;
|
||||
@@ -119,7 +119,7 @@ public final class UberClassLoader extends ClassLoader
|
||||
{
|
||||
URL url = null;
|
||||
|
||||
for (PluginWrapper plugin : plugins)
|
||||
for (InstalledPlugin plugin : plugins)
|
||||
{
|
||||
ClassLoader cl = plugin.getClassLoader();
|
||||
|
||||
@@ -149,7 +149,7 @@ public final class UberClassLoader extends ClassLoader
|
||||
{
|
||||
List<URL> urls = Lists.newArrayList();
|
||||
|
||||
for (PluginWrapper plugin : plugins)
|
||||
for (InstalledPlugin plugin : plugins)
|
||||
{
|
||||
ClassLoader cl = plugin.getClassLoader();
|
||||
|
||||
@@ -194,5 +194,5 @@ public final class UberClassLoader extends ClassLoader
|
||||
Maps.newConcurrentMap();
|
||||
|
||||
/** Field description */
|
||||
private final Iterable<PluginWrapper> plugins;
|
||||
private final Iterable<InstalledPlugin> plugins;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user