/*
* Copyright 2017 ThoughtWorks, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.thoughtworks.go.plugin.infra;
import com.thoughtworks.go.plugin.api.GoPlugin;
import com.thoughtworks.go.plugin.api.exceptions.UnhandledRequestTypeException;
import com.thoughtworks.go.plugin.api.info.PluginDescriptor;
import com.thoughtworks.go.plugin.api.request.GoPluginApiRequest;
import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse;
import com.thoughtworks.go.plugin.infra.commons.PluginUploadResponse;
import com.thoughtworks.go.plugin.infra.listeners.DefaultPluginJarChangeListener;
import com.thoughtworks.go.plugin.infra.listeners.PluginsListListener;
import com.thoughtworks.go.plugin.infra.listeners.PluginsZipUpdater;
import com.thoughtworks.go.plugin.infra.monitor.DefaultPluginJarLocationMonitor;
import com.thoughtworks.go.plugin.infra.plugininfo.DefaultPluginRegistry;
import com.thoughtworks.go.plugin.infra.plugininfo.GoPluginDescriptor;
import com.thoughtworks.go.util.FileUtil;
import com.thoughtworks.go.util.SystemEnvironment;
import org.apache.commons.io.FileUtils;
import org.apache.http.HttpStatus;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
import java.util.*;
import static com.thoughtworks.go.util.SystemEnvironment.PLUGIN_BUNDLE_PATH;
import static java.lang.Double.parseDouble;
@Service
public class DefaultPluginManager implements PluginManager {
private static final Logger LOGGER = Logger.getLogger(DefaultPluginManager.class);
private final DefaultPluginJarLocationMonitor monitor;
private DefaultPluginRegistry registry;
private final DefaultPluginJarChangeListener defaultPluginJarChangeListener;
private SystemEnvironment systemEnvironment;
private File bundleLocation;
private GoPluginOSGiFramework goPluginOSGiFramework;
private PluginWriter pluginWriter;
private PluginValidator pluginValidator;
private PluginsZipUpdater pluginsZipUpdater;
private PluginsListListener pluginsListListener;
private final Set<PluginDescriptor> initializedPlugins = new HashSet<>();
private PluginRequestProcessorRegistry requestProcesRegistry;
@Autowired
public DefaultPluginManager(DefaultPluginJarLocationMonitor monitor, DefaultPluginRegistry registry, GoPluginOSGiFramework goPluginOSGiFramework,
DefaultPluginJarChangeListener defaultPluginJarChangeListener, PluginRequestProcessorRegistry requestProcesRegistry, PluginWriter pluginWriter,
PluginValidator pluginValidator, SystemEnvironment systemEnvironment, PluginsZipUpdater pluginsZipUpdater, PluginsListListener pluginsListListener) {
this.monitor = monitor;
this.registry = registry;
this.defaultPluginJarChangeListener = defaultPluginJarChangeListener;
this.requestProcesRegistry = requestProcesRegistry;
this.systemEnvironment = systemEnvironment;
this.pluginsZipUpdater = pluginsZipUpdater;
this.pluginsListListener = pluginsListListener;
bundleLocation = bundlePath();
this.goPluginOSGiFramework = goPluginOSGiFramework;
this.pluginWriter = pluginWriter;
this.pluginValidator = pluginValidator;
}
@Override
public List<GoPluginDescriptor> plugins() {
return registry.plugins();
}
public PluginUploadResponse addPlugin(File uploadedPlugin, String filename) {
if (!pluginValidator.namecheckForJar(filename)) {
Map<Integer, String> errors = new HashMap<>();
errors.put(HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE, "Please upload a jar.");
return PluginUploadResponse.create(false, null, errors);
}
return pluginWriter.addPlugin(uploadedPlugin, filename);
}
@Override
public GoPluginDescriptor getPluginDescriptorFor(String pluginId) {
return registry.getPlugin(pluginId);
}
@Override
public <T> void doOnAll(Class<T> serviceReferenceClass, Action<T> actionToDoOnEachRegisteredServiceWhichMatches) {
goPluginOSGiFramework.doOnAll(serviceReferenceClass, actionToDoOnEachRegisteredServiceWhichMatches);
}
@Override
public <T> void doOnAll(Class<T> serviceReferenceClass, Action<T> actionToDoOnEachRegisteredServiceWhichMatches, ExceptionHandler<T> exceptionHandler) {
goPluginOSGiFramework.doOnAllWithExceptionHandling(serviceReferenceClass, actionToDoOnEachRegisteredServiceWhichMatches, exceptionHandler);
}
@Override
public <T, R> R doOn(Class<T> serviceReferenceClass, String pluginId, ActionWithReturn<T, R> actionToDoOnTheRegisteredServiceWhichMatches) {
return goPluginOSGiFramework.doOn(serviceReferenceClass, pluginId, actionToDoOnTheRegisteredServiceWhichMatches);
}
@Override
public <T> void doOn(Class<T> serviceReferenceClass, String pluginId, Action<T> action) {
goPluginOSGiFramework.doOn(serviceReferenceClass, pluginId, action);
}
@Override
public <T> void doOnIfHasReference(Class<T> serviceReferenceClass, String pluginId, Action<T> action) {
if (goPluginOSGiFramework.hasReferenceFor(serviceReferenceClass, pluginId)) {
doOn(serviceReferenceClass, pluginId, action);
}
}
@Override
public void startInfrastructure(boolean shouldPoll) {
removeBundleDirectory();
goPluginOSGiFramework.start();
addPluginChangeListener(new PluginChangeListener() {
@Override
public void pluginLoaded(GoPluginDescriptor pluginDescriptor) {
}
@Override
public void pluginUnLoaded(GoPluginDescriptor pluginDescriptor) {
synchronized (initializedPlugins) {
initializedPlugins.remove(pluginDescriptor);
}
}
});
monitor.addPluginJarChangeListener(defaultPluginJarChangeListener);
if (shouldPoll) {
monitor.start();
} else {
monitor.oneShot();
}
}
@Override
public void registerPluginsFolderChangeListener() {
monitor.addPluginsFolderChangeListener(pluginsZipUpdater);
monitor.addPluginsFolderChangeListener(pluginsListListener);
}
@Override
public void stopInfrastructure() {
goPluginOSGiFramework.stop();
monitor.stop();
}
@Override
public void addPluginChangeListener(PluginChangeListener pluginChangeListener, Class<?>... serviceReferenceClass) {
PluginChangeListener filterChangeListener = new FilterChangeListener(goPluginOSGiFramework, pluginChangeListener, serviceReferenceClass);
goPluginOSGiFramework.addPluginChangeListener(filterChangeListener);
}
@Override
public GoPluginApiResponse submitTo(final String pluginId, final GoPluginApiRequest apiRequest) {
return goPluginOSGiFramework.doOn(GoPlugin.class, pluginId, new ActionWithReturn<GoPlugin, GoPluginApiResponse>() {
@Override
public GoPluginApiResponse execute(GoPlugin plugin, GoPluginDescriptor pluginDescriptor) {
ensureInitializerInvoked(pluginDescriptor, plugin);
try {
return plugin.handle(apiRequest);
} catch (UnhandledRequestTypeException e) {
LOGGER.error(e.getMessage());
LOGGER.debug(e.getMessage(), e);
throw new RuntimeException(e);
}
}
});
}
private void ensureInitializerInvoked(GoPluginDescriptor pluginDescriptor, GoPlugin plugin) {
synchronized (initializedPlugins) {
if (initializedPlugins.contains(pluginDescriptor)) {
return;
}
initializedPlugins.add(pluginDescriptor);
PluginAwareDefaultGoApplicationAccessor accessor = new PluginAwareDefaultGoApplicationAccessor(pluginDescriptor, requestProcesRegistry);
plugin.initializeGoApplicationAccessor(accessor);
}
}
@Override
public boolean hasReferenceFor(Class serviceReferenceClass, String pluginId) {
return goPluginOSGiFramework.hasReferenceFor(serviceReferenceClass, pluginId);
}
@Override
public boolean isPluginOfType(final String extension, String pluginId) {
return hasReferenceFor(GoPlugin.class, pluginId) && goPluginOSGiFramework.doOn(GoPlugin.class, pluginId, new ActionWithReturn<GoPlugin, Boolean>() {
@Override
public Boolean execute(GoPlugin plugin, GoPluginDescriptor pluginDescriptor) {
return extension.equals(plugin.pluginIdentifier().getExtension());
}
});
}
@Override
public String resolveExtensionVersion(String pluginId, final List<String> goSupportedExtensionVersions) {
String resolvedExtensionVersion = doOn(GoPlugin.class, pluginId, new ActionWithReturn<GoPlugin, String>() {
@Override
public String execute(GoPlugin goPlugin, GoPluginDescriptor pluginDescriptor) {
List<String> pluginSupportedVersions = goPlugin.pluginIdentifier().getSupportedExtensionVersions();
String currentMaxVersion = "0";
for (String pluginSupportedVersion : pluginSupportedVersions) {
if (goSupportedExtensionVersions.contains(pluginSupportedVersion) && parseDouble(currentMaxVersion) < parseDouble(pluginSupportedVersion)) {
currentMaxVersion = pluginSupportedVersion;
}
}
return currentMaxVersion;
}
});
if ("0".equals(resolvedExtensionVersion)) {
throw new RuntimeException(String.format("Could not find matching extension version between Plugin[%s] and Go", pluginId));
}
return resolvedExtensionVersion;
}
private void removeBundleDirectory() {
try {
FileUtils.deleteDirectory(bundleLocation);
} catch (IOException e) {
throw new RuntimeException(String.format("Failed to copy delete bundle directory %s", bundleLocation), e);
}
}
private File bundlePath() {
File bundleDir = new File(systemEnvironment.get(PLUGIN_BUNDLE_PATH));
FileUtil.validateAndCreateDirectory(bundleDir);
return bundleDir;
}
private static class FilterChangeListener implements PluginChangeListener {
private final GoPluginOSGiFramework goPluginOSGiFramework;
private final PluginChangeListener pluginChangeListenerDelegate;
private final Class<?>[] serviceReferences;
public FilterChangeListener(GoPluginOSGiFramework goPluginOSGiFramework,
PluginChangeListener pluginChangeListener, Class<?>... serviceReferenceClass) {
this.goPluginOSGiFramework = goPluginOSGiFramework;
pluginChangeListenerDelegate = pluginChangeListener;
serviceReferences = serviceReferenceClass;
}
@Override
public void pluginLoaded(GoPluginDescriptor descriptor) {
String pluginId = descriptor.id();
if (shouldCallDelegate(pluginId)) {
pluginChangeListenerDelegate.pluginLoaded(descriptor);
}
}
private boolean shouldCallDelegate(String pluginId) {
for (Class<?> serviceReference : serviceReferences) {
if (goPluginOSGiFramework.hasReferenceFor(serviceReference, pluginId)) {
return true;
}
}
return false;
}
@Override
public void pluginUnLoaded(GoPluginDescriptor descriptor) {
pluginChangeListenerDelegate.pluginUnLoaded(descriptor);
}
}
}