Merged in feature/migration (pull request #249)

Feature migration
This commit is contained in:
Sebastian Sdorra
2019-05-23 15:04:37 +00:00
32 changed files with 1669 additions and 1148 deletions

View File

@@ -37,6 +37,7 @@ import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.assistedinject.Assisted;
import org.apache.shiro.guice.web.ShiroWebModule;
import org.jboss.resteasy.plugins.guice.GuiceResteasyBootstrapServletContextListener;
import org.slf4j.Logger;
@@ -55,6 +56,7 @@ import sonia.scm.upgrade.UpgradeManager;
import sonia.scm.user.UserManager;
import sonia.scm.util.IOUtil;
import javax.inject.Inject;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import java.util.Collections;
@@ -77,9 +79,12 @@ public class ScmContextListener extends GuiceResteasyBootstrapServletContextList
private final Set<PluginWrapper> plugins;
private Injector injector;
//~--- constructors ---------------------------------------------------------
public ScmContextListener(ClassLoader parent, Set<PluginWrapper> plugins)
public interface Factory {
ScmContextListener create(ClassLoader parent, Set<PluginWrapper> plugins);
}
@Inject
public ScmContextListener(@Assisted ClassLoader parent, @Assisted Set<PluginWrapper> plugins)
{
this.parent = parent;
this.plugins = plugins;
@@ -127,9 +132,6 @@ public class ScmContextListener extends GuiceResteasyBootstrapServletContextList
List<Module> moduleList = Lists.newArrayList();
moduleList.add(new ResteasyModule());
moduleList.add(new ScmInitializerModule());
moduleList.add(new ScmEventBusModule());
moduleList.add(new EagerSingletonModule());
moduleList.add(ShiroWebModule.guiceFilterModule());
moduleList.add(new WebElementModule(pluginLoader));
moduleList.add(new ScmServletModule(context, pluginLoader, overrides));

View File

@@ -212,12 +212,7 @@ public class ScmServletModule extends ServletModule
{
install(ThrowingProviderBinder.forModule(this));
SCMContextProvider context = SCMContext.getContext();
bind(SCMContextProvider.class).toInstance(context);
ScmConfiguration config = getScmConfiguration();
CipherUtil cu = CipherUtil.getInstance();
bind(NamespaceStrategy.class).toProvider(NamespaceStrategyProvider.class);
@@ -234,21 +229,11 @@ public class ScmServletModule extends ServletModule
bind(ScmEventBus.class).toInstance(ScmEventBus.getInstance());
// bind core
bind(ConfigurationStoreFactory.class, JAXBConfigurationStoreFactory.class);
bind(ConfigurationEntryStoreFactory.class, JAXBConfigurationEntryStoreFactory.class);
bind(DataStoreFactory.class, JAXBDataStoreFactory.class);
bind(BlobStoreFactory.class, FileBlobStoreFactory.class);
bind(ScmConfiguration.class).toInstance(config);
bind(PluginLoader.class).toInstance(pluginLoader);
bind(PluginManager.class, DefaultPluginManager.class);
// bind scheduler
bind(Scheduler.class).to(QuartzScheduler.class);
// note CipherUtil uses an other generator
bind(KeyGenerator.class).to(DefaultKeyGenerator.class);
bind(CipherHandler.class).toInstance(cu.getCipherHandler());
bind(FileSystem.class, DefaultFileSystem.class);
// bind health check stuff
bind(HealthCheckContextListener.class);
@@ -327,7 +312,6 @@ public class ScmServletModule extends ServletModule
bind(ObjectMapper.class).toProvider(ObjectMapperProvider.class);
// bind events
// bind(LastModifiedUpdateListener.class);
bind(AccessTokenCookieIssuer.class).to(DefaultAccessTokenCookieIssuer.class);
bind(PushStateDispatcher.class).toProvider(PushStateDispatcherProvider.class);

View File

@@ -1,9 +1,9 @@
/**
* Copyright (c) 2010, Sebastian Sdorra All rights reserved.
*
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* <p>
* 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
@@ -11,7 +11,7 @@
* 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.
*
* <p>
* 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
@@ -22,67 +22,65 @@
* 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.
*
* <p>
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.boot;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Files;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.assistedinject.FactoryModuleBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.EagerSingletonModule;
import sonia.scm.SCMContext;
import sonia.scm.ScmContextListener;
import sonia.scm.ScmEventBusModule;
import sonia.scm.ScmInitializerModule;
import sonia.scm.Stage;
import sonia.scm.event.ScmEventBus;
import sonia.scm.plugin.DefaultPluginLoader;
import sonia.scm.plugin.Plugin;
import sonia.scm.plugin.PluginException;
import sonia.scm.plugin.PluginLoadException;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.plugin.PluginWrapper;
import sonia.scm.plugin.PluginsInternal;
import sonia.scm.plugin.SmpArchive;
import sonia.scm.update.UpdateEngine;
import sonia.scm.util.ClassLoaders;
import sonia.scm.util.IOUtil;
//~--- JDK imports ------------------------------------------------------------
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.xml.bind.DataBindingException;
import javax.xml.bind.JAXB;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
*
* @author Sebastian Sdorra
*/
public class BootstrapContextListener implements ServletContextListener
{
public class BootstrapContextListener implements ServletContextListener {
/** Field description */
private static final String DIRECTORY_PLUGINS = "plugins";
@@ -109,22 +107,16 @@ public class BootstrapContextListener implements ServletContextListener
* @param sce
*/
@Override
public void contextDestroyed(ServletContextEvent sce)
{
public void contextDestroyed(ServletContextEvent sce) {
contextListener.contextDestroyed(sce);
for (PluginWrapper plugin : contextListener.getPlugins())
{
for (PluginWrapper plugin : contextListener.getPlugins()) {
ClassLoader pcl = plugin.getClassLoader();
if (pcl instanceof Closeable)
{
try
{
if (pcl instanceof Closeable) {
try {
((Closeable) pcl).close();
}
catch (IOException ex)
{
} catch (IOException ex) {
logger.warn("could not close plugin classloader", ex);
}
}
@@ -141,43 +133,68 @@ public class BootstrapContextListener implements ServletContextListener
* @param sce
*/
@Override
public void contextInitialized(ServletContextEvent sce)
{
public void contextInitialized(ServletContextEvent sce) {
context = sce.getServletContext();
File pluginDirectory = getPluginDirectory();
try
{
createContextListener(pluginDirectory);
contextListener.contextInitialized(sce);
// register for restart events
if (!registered && (SCMContext.getContext().getStage() == Stage.DEVELOPMENT)) {
logger.info("register for restart events");
ScmEventBus.getInstance().register(this);
registered = true;
}
}
private void createContextListener(File pluginDirectory) {
try {
if (!isCorePluginExtractionDisabled()) {
extractCorePlugins(context, pluginDirectory);
} else {
logger.info("core plugin extraction is disabled");
}
ClassLoader cl =
ClassLoaders.getContextClassLoader(BootstrapContextListener.class);
ClassLoader cl = ClassLoaders.getContextClassLoader(BootstrapContextListener.class);
Set<PluginWrapper> plugins = PluginsInternal.collectPlugins(cl,
pluginDirectory.toPath());
Set<PluginWrapper> plugins = PluginsInternal.collectPlugins(cl, pluginDirectory.toPath());
contextListener = new ScmContextListener(cl, plugins);
}
catch (IOException ex)
{
PluginLoader pluginLoader = new DefaultPluginLoader(context, cl, plugins);
Injector bootstrapInjector = createBootstrapInjector(pluginLoader);
processUpdates(pluginLoader, bootstrapInjector);
contextListener = bootstrapInjector.getInstance(ScmContextListener.Factory.class).create(cl, plugins);
} catch (IOException ex) {
throw new PluginLoadException("could not load plugins", ex);
}
}
contextListener.contextInitialized(sce);
// register for restart events
if (!registered
&& (SCMContext.getContext().getStage() == Stage.DEVELOPMENT))
{
logger.info("register for restart events");
ScmEventBus.getInstance().register(this);
registered = true;
}
private Injector createBootstrapInjector(PluginLoader pluginLoader) {
Module scmContextListenerModule = new ScmContextListenerModule();
BootstrapModule bootstrapModule = new BootstrapModule(pluginLoader);
ScmInitializerModule scmInitializerModule = new ScmInitializerModule();
EagerSingletonModule eagerSingletonModule = new EagerSingletonModule();
ScmEventBusModule scmEventBusModule = new ScmEventBusModule();
return Guice.createInjector(
bootstrapModule,
scmContextListenerModule,
scmEventBusModule,
scmInitializerModule,
eagerSingletonModule
);
}
private void processUpdates(PluginLoader pluginLoader, Injector bootstrapInjector) {
Injector updateInjector = bootstrapInjector.createChildInjector(new UpdateStepModule(pluginLoader));
UpdateEngine updateEngine = updateInjector.getInstance(UpdateEngine.class);
updateEngine.update();
}
private boolean isCorePluginExtractionDisabled() {
@@ -195,41 +212,32 @@ public class BootstrapContextListener implements ServletContextListener
* @throws IOException
*/
private void extractCorePlugin(ServletContext context, File pluginDirectory,
PluginIndexEntry entry)
throws IOException
{
PluginIndexEntry entry)
throws IOException {
URL url = context.getResource(PLUGIN_DIRECTORY.concat(entry.getName()));
SmpArchive archive = SmpArchive.create(url);
Plugin plugin = archive.getPlugin();
File directory = PluginsInternal.createPluginDirectory(pluginDirectory,
plugin);
plugin);
File checksumFile = PluginsInternal.getChecksumFile(directory);
if (!directory.exists())
{
if (!directory.exists()) {
logger.warn("install plugin {}", plugin.getInformation().getId());
PluginsInternal.extract(archive, entry.getChecksum(), directory,
checksumFile, true);
}
else if (!checksumFile.exists())
{
} else if (!checksumFile.exists()) {
logger.warn("plugin directory {} exists without checksum file.",
directory);
PluginsInternal.extract(archive, entry.getChecksum(), directory,
checksumFile, true);
}
else
{
} else {
String checksum = Files.toString(checksumFile, Charsets.UTF_8).trim();
if (checksum.equals(entry.getChecksum()))
{
if (checksum.equals(entry.getChecksum())) {
logger.debug("plugin {} is up to date",
plugin.getInformation().getId());
}
else
{
} else {
logger.warn("checksum mismatch of pluing {}, start update",
plugin.getInformation().getId());
PluginsInternal.extract(archive, entry.getChecksum(), directory,
@@ -247,14 +255,12 @@ public class BootstrapContextListener implements ServletContextListener
*
* @throws IOException
*/
private void extractCorePlugins(ServletContext context, File pluginDirectory) throws IOException
{
private void extractCorePlugins(ServletContext context, File pluginDirectory) throws IOException {
IOUtil.mkdirs(pluginDirectory);
PluginIndex index = readCorePluginIndex(context);
for (PluginIndexEntry entry : index)
{
for (PluginIndexEntry entry : index) {
extractCorePlugin(context, pluginDirectory, entry);
}
}
@@ -267,27 +273,20 @@ public class BootstrapContextListener implements ServletContextListener
*
* @return
*/
private PluginIndex readCorePluginIndex(ServletContext context)
{
private PluginIndex readCorePluginIndex(ServletContext context) {
PluginIndex index = null;
try
{
try {
URL indexUrl = context.getResource(PLUGIN_COREINDEX);
if (indexUrl == null)
{
if (indexUrl == null) {
throw new PluginException("no core plugin index found");
}
index = JAXB.unmarshal(indexUrl, PluginIndex.class);
}
catch (MalformedURLException ex)
{
} catch (MalformedURLException ex) {
throw new PluginException("could not load core plugin index", ex);
}
catch (DataBindingException ex)
{
} catch (DataBindingException ex) {
throw new PluginException("could not unmarshall core plugin index", ex);
}
@@ -302,8 +301,7 @@ public class BootstrapContextListener implements ServletContextListener
*
* @return
*/
private File getPluginDirectory()
{
private File getPluginDirectory() {
File baseDirectory = SCMContext.getContext().getBaseDirectory();
return new File(baseDirectory, DIRECTORY_PLUGINS);
@@ -315,13 +313,12 @@ public class BootstrapContextListener implements ServletContextListener
* Class description
*
*
* @version Enter version here..., 14/07/09
* @author Enter your name here...
* @version Enter version here..., 14/07/09
* @author Enter your name here...
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "plugin-index")
private static class PluginIndex implements Iterable<PluginIndexEntry>
{
private static class PluginIndex implements Iterable<PluginIndexEntry> {
/**
* Method description
@@ -330,8 +327,7 @@ public class BootstrapContextListener implements ServletContextListener
* @return
*/
@Override
public Iterator<PluginIndexEntry> iterator()
{
public Iterator<PluginIndexEntry> iterator() {
return getPlugins().iterator();
}
@@ -343,10 +339,8 @@ public class BootstrapContextListener implements ServletContextListener
*
* @return
*/
public List<PluginIndexEntry> getPlugins()
{
if (plugins == null)
{
public List<PluginIndexEntry> getPlugins() {
if (plugins == null) {
plugins = ImmutableList.of();
}
@@ -365,13 +359,12 @@ public class BootstrapContextListener implements ServletContextListener
* Class description
*
*
* @version Enter version here..., 14/07/09
* @author Enter your name here...
* @version Enter version here..., 14/07/09
* @author Enter your name here...
*/
@XmlRootElement(name = "plugins")
@XmlAccessorType(XmlAccessType.FIELD)
private static class PluginIndexEntry
{
private static class PluginIndexEntry {
/**
* Method description
@@ -379,8 +372,7 @@ public class BootstrapContextListener implements ServletContextListener
*
* @return
*/
public String getChecksum()
{
public String getChecksum() {
return checksum;
}
@@ -390,8 +382,7 @@ public class BootstrapContextListener implements ServletContextListener
*
* @return
*/
public String getName()
{
public String getName() {
return name;
}
@@ -415,4 +406,11 @@ public class BootstrapContextListener implements ServletContextListener
/** Field description */
private boolean registered = false;
private static class ScmContextListenerModule extends AbstractModule {
@Override
protected void configure() {
install(new FactoryModuleBuilder().build(ScmContextListener.Factory.class));
}
}
}

View File

@@ -0,0 +1,86 @@
package sonia.scm.boot;
import com.google.inject.AbstractModule;
import com.google.inject.throwingproviders.ThrowingProviderBinder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.ClassOverrides;
import sonia.scm.SCMContext;
import sonia.scm.SCMContextProvider;
import sonia.scm.io.DefaultFileSystem;
import sonia.scm.io.FileSystem;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.repository.RepositoryLocationResolver;
import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
import sonia.scm.security.CipherHandler;
import sonia.scm.security.CipherUtil;
import sonia.scm.security.DefaultKeyGenerator;
import sonia.scm.security.KeyGenerator;
import sonia.scm.store.BlobStoreFactory;
import sonia.scm.store.ConfigurationEntryStoreFactory;
import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.store.DataStoreFactory;
import sonia.scm.store.FileBlobStoreFactory;
import sonia.scm.store.JAXBConfigurationEntryStoreFactory;
import sonia.scm.store.JAXBConfigurationStoreFactory;
import sonia.scm.store.JAXBDataStoreFactory;
public class BootstrapModule extends AbstractModule {
private static final Logger LOG = LoggerFactory.getLogger(BootstrapModule.class);
private final ClassOverrides overrides;
private final PluginLoader pluginLoader;
BootstrapModule(PluginLoader pluginLoader) {
this.overrides = ClassOverrides.findOverrides(pluginLoader.getUberClassLoader());
this.pluginLoader = pluginLoader;
}
@Override
protected void configure() {
install(ThrowingProviderBinder.forModule(this));
SCMContextProvider context = SCMContext.getContext();
bind(SCMContextProvider.class).toInstance(context);
bind(KeyGenerator.class).to(DefaultKeyGenerator.class);
bind(RepositoryLocationResolver.class).to(PathBasedRepositoryLocationResolver.class);
bind(FileSystem.class, DefaultFileSystem.class);
// note CipherUtil uses an other generator
bind(CipherHandler.class).toInstance(CipherUtil.getInstance().getCipherHandler());
// bind core
bind(ConfigurationStoreFactory.class, JAXBConfigurationStoreFactory.class);
bind(ConfigurationEntryStoreFactory.class, JAXBConfigurationEntryStoreFactory.class);
bind(DataStoreFactory.class, JAXBDataStoreFactory.class);
bind(BlobStoreFactory.class, FileBlobStoreFactory.class);
bind(PluginLoader.class).toInstance(pluginLoader);
}
private <T> void bind(Class<T> clazz, Class<? extends T> defaultImplementation) {
Class<? extends T> implementation = find(clazz, defaultImplementation);
LOG.debug("bind {} to {}", clazz, implementation);
bind(clazz).to(implementation);
}
private <T> Class<? extends T> find(Class<T> clazz, Class<? extends T> defaultImplementation) {
Class<? extends T> implementation = overrides.getOverride(clazz);
if (implementation != null) {
LOG.info("found override {} for {}", implementation, clazz);
} else {
implementation = defaultImplementation;
LOG.trace(
"no override available for {}, using default implementation {}",
clazz, implementation);
}
return implementation;
}
}

View File

@@ -0,0 +1,24 @@
package sonia.scm.boot;
import com.google.inject.AbstractModule;
import com.google.inject.multibindings.Multibinder;
import sonia.scm.migration.UpdateStep;
import sonia.scm.plugin.PluginLoader;
class UpdateStepModule extends AbstractModule {
private final PluginLoader pluginLoader;
UpdateStepModule(PluginLoader pluginLoader) {
this.pluginLoader = pluginLoader;
}
@Override
protected void configure() {
Multibinder<UpdateStep> updateStepBinder = Multibinder.newSetBinder(binder(), UpdateStep.class);
pluginLoader
.getExtensionProcessor()
.byExtensionPoint(UpdateStep.class)
.forEach(stepClass -> updateStepBinder.addBinding().to(stepClass));
}
}

View File

@@ -0,0 +1,86 @@
package sonia.scm.update;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.migration.UpdateException;
import sonia.scm.migration.UpdateStep;
import sonia.scm.store.ConfigurationEntryStore;
import sonia.scm.store.ConfigurationEntryStoreFactory;
import javax.inject.Inject;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import static java.util.stream.Collectors.toList;
public class UpdateEngine {
public static final Logger LOG = LoggerFactory.getLogger(UpdateEngine.class);
private static final String STORE_NAME = "executedUpdates";
private final List<UpdateStep> steps;
private final ConfigurationEntryStore<UpdateVersionInfo> store;
@Inject
public UpdateEngine(Set<UpdateStep> steps, ConfigurationEntryStoreFactory storeFactory) {
this.steps = sortSteps(steps);
this.store = storeFactory.withType(UpdateVersionInfo.class).withName(STORE_NAME).build();
}
private List<UpdateStep> sortSteps(Set<UpdateStep> steps) {
LOG.trace("sorting available update steps:");
List<UpdateStep> sortedSteps = steps.stream()
.sorted(Comparator.comparing(UpdateStep::getTargetVersion).reversed())
.collect(toList());
sortedSteps.forEach(step -> LOG.trace("{} for version {}", step.getAffectedDataType(), step.getTargetVersion()));
return sortedSteps;
}
public void update() {
steps
.stream()
.filter(this::notRunYet)
.forEach(this::execute);
}
private void execute(UpdateStep updateStep) {
try {
LOG.info("running update step for type {} and version {}",
updateStep.getAffectedDataType(),
updateStep.getTargetVersion()
);
updateStep.doUpdate();
} catch (Exception e) {
throw new UpdateException(
String.format(
"could not execute update for type %s to version %s in class %s",
updateStep.getAffectedDataType(),
updateStep.getTargetVersion(),
updateStep.getClass()),
e);
}
UpdateVersionInfo newVersionInfo = new UpdateVersionInfo(updateStep.getTargetVersion().getParsedVersion());
store.put(updateStep.getAffectedDataType(), newVersionInfo);
}
private boolean notRunYet(UpdateStep updateStep) {
LOG.trace("checking whether to run update step for type {} and version {}",
updateStep.getAffectedDataType(),
updateStep.getTargetVersion()
);
UpdateVersionInfo updateVersionInfo = store.get(updateStep.getAffectedDataType());
if (updateVersionInfo == null) {
LOG.trace("no updates for type {} run yet; step will be executed", updateStep.getAffectedDataType());
return true;
}
boolean result = updateStep.getTargetVersion().isNewer(updateVersionInfo.getLatestVersion());
LOG.trace("latest version for type {}: {}; step will be executed: {}",
updateStep.getAffectedDataType(),
updateVersionInfo.getLatestVersion(),
result
);
return result;
}
}

View File

@@ -0,0 +1,22 @@
package sonia.scm.update;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "latest-version")
@XmlAccessorType(XmlAccessType.FIELD)
public class UpdateVersionInfo {
private String latestVersion;
public UpdateVersionInfo() {
}
public UpdateVersionInfo(String latestVersion) {
this.latestVersion = latestVersion;
}
public String getLatestVersion() {
return latestVersion;
}
}