mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-08-04 22:40:46 +02:00
Merged in feature/java_9_and_above (pull request #400)
Feature/java 9 and above
This commit is contained in:
11
CHANGELOG.md
11
CHANGELOG.md
@@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
### Added
|
||||
- Support for Java versions > 8
|
||||
- Simple ClassLoaderLifeCycle to fix integration tests on Java > 8
|
||||
|
||||
### Changed
|
||||
- Upgrade [Legman](https://github.com/sdorra/legman) to v1.6.2 in order to fix execution on Java versions > 8
|
||||
- Upgrade [Lombok](https://projectlombok.org/) to version 1.18.10 in order to fix build on Java versions > 8
|
||||
- Upgrade [Mockito](https://site.mockito.org/) to version 2.28.2 in order to fix tests on Java versions > 8
|
||||
- Upgrade smp-maven-plugin to version 1.0.0-rc3
|
||||
|
||||
## 2.0.0-rc3 - 2020-01-31
|
||||
### Fixed
|
||||
- Broken plugin order fixed
|
||||
|
||||
4
Jenkinsfile
vendored
4
Jenkinsfile
vendored
@@ -37,7 +37,7 @@ node('docker') {
|
||||
}
|
||||
|
||||
stage('Integration Test') {
|
||||
mvn 'verify -Pit -pl :scm-webapp,:scm-it -Dmaven.test.failure.ignore=true -DClassLoaderLeakPreventor.threadWaitMs=10'
|
||||
mvn 'verify -Pit -pl :scm-webapp,:scm-it -Dmaven.test.failure.ignore=true'
|
||||
}
|
||||
|
||||
stage('SonarQube') {
|
||||
@@ -108,7 +108,7 @@ String mainBranch
|
||||
|
||||
Maven setupMavenBuild() {
|
||||
// Keep this version number in sync with .mvn/maven-wrapper.properties
|
||||
Maven mvn = new MavenInDocker(this, '3.5.2-jdk-8')
|
||||
Maven mvn = new MavenInDocker(this, '3.6.3-jdk-11')
|
||||
|
||||
if (isMainBranch()) {
|
||||
// Release starts javadoc, which takes very long, so do only for certain branches
|
||||
|
||||
4
lombok.config
Normal file
4
lombok.config
Normal file
@@ -0,0 +1,4 @@
|
||||
# since v1.16.20 the @ConstructorProperties is no longer added to constructors,
|
||||
# but this annotation seem to be used by Jackson.
|
||||
# Not sure if this is realy required, but without sonia.scm.api.rest.JSONContextResolverTest breaks
|
||||
lombok.anyConstructor.addConstructorProperties = true
|
||||
49
pom.xml
49
pom.xml
@@ -212,7 +212,7 @@
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.16.18</version>
|
||||
<version>1.18.10</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -285,9 +285,9 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>${hibernate-validator.version}</version>
|
||||
<version>${hibernate-validator.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JUnit 5 -->
|
||||
@@ -434,7 +434,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.0.1</version>
|
||||
<version>3.1.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
@@ -456,7 +456,7 @@
|
||||
<plugin>
|
||||
<groupId>sonia.scm.maven</groupId>
|
||||
<artifactId>smp-maven-plugin</artifactId>
|
||||
<version>1.0.0-rc2</version>
|
||||
<version>1.0.0-rc3</version>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
@@ -625,6 +625,7 @@
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
<docencoding>${project.build.sourceEncoding}</docencoding>
|
||||
<breakiterator>true</breakiterator>
|
||||
<source>${project.build.javaLevel}</source>
|
||||
<version>true</version>
|
||||
<author>true</author>
|
||||
<keywords>true</keywords>
|
||||
@@ -664,11 +665,11 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-release-plugin</artifactId>
|
||||
<version>2.3.2</version>
|
||||
<version>2.5.3</version>
|
||||
<configuration>
|
||||
<pushChanges>false</pushChanges>
|
||||
<localCheckout>true</localCheckout>
|
||||
<releaseProfiles>release,APIviz,doc,nativepkg</releaseProfiles>
|
||||
<releaseProfiles>release,doc,nativepkg</releaseProfiles>
|
||||
<tagNameFormat>@{project.version}</tagNameFormat>
|
||||
<autoVersionSubmodules>true</autoVersionSubmodules>
|
||||
</configuration>
|
||||
@@ -798,32 +799,6 @@
|
||||
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>APIviz</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>sonia.apiviz</name>
|
||||
<value>true</value>
|
||||
</property>
|
||||
</activation>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<configuration>
|
||||
<failOnError>false</failOnError>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</profile>
|
||||
|
||||
</profiles>
|
||||
|
||||
<distributionManagement>
|
||||
@@ -844,7 +819,7 @@
|
||||
|
||||
<properties>
|
||||
<!-- test libraries -->
|
||||
<mockito.version>2.23.0</mockito.version>
|
||||
<mockito.version>2.28.2</mockito.version>
|
||||
<hamcrest.version>1.3</hamcrest.version>
|
||||
<junit.version>5.2.0</junit.version>
|
||||
|
||||
@@ -863,7 +838,7 @@
|
||||
<hibernate-validator.version>6.1.0.Final</hibernate-validator.version>
|
||||
|
||||
<!-- event bus -->
|
||||
<legman.version>1.6.1</legman.version>
|
||||
<legman.version>1.6.2</legman.version>
|
||||
|
||||
<!-- webserver -->
|
||||
<jetty.version>9.4.22.v20191022</jetty.version>
|
||||
@@ -885,8 +860,8 @@
|
||||
<yarn.version>1.16.0</yarn.version>
|
||||
|
||||
<!-- build properties -->
|
||||
<project.build.javaLevel>1.8</project.build.javaLevel>
|
||||
<project.test.javaLevel>1.8</project.test.javaLevel>
|
||||
<project.build.javaLevel>8</project.build.javaLevel>
|
||||
<project.test.javaLevel>8</project.test.javaLevel>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<netbeans.hint.license>SCM-BSD</netbeans.hint.license>
|
||||
<jdk.classifier />
|
||||
|
||||
@@ -40,7 +40,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* The I18n annotation is used by the {@link I18nMessages} class to define the
|
||||
* The I18n annotation is used by the I18nMessages class to define the
|
||||
* resource bundle key.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
@@ -53,7 +53,7 @@ public @interface I18n
|
||||
{
|
||||
/**
|
||||
* Returns the key for the resource bundle.
|
||||
*
|
||||
*
|
||||
* @return resource bundle key
|
||||
*/
|
||||
String value();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
@@ -15,7 +15,7 @@
|
||||
<name>scm-core</name>
|
||||
|
||||
<dependencies>
|
||||
|
||||
|
||||
<!-- servlet api -->
|
||||
|
||||
<dependency>
|
||||
@@ -55,13 +55,13 @@
|
||||
</dependency>
|
||||
|
||||
<!-- security -->
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-core</artifactId>
|
||||
<version>${shiro.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- injection -->
|
||||
|
||||
<dependency>
|
||||
@@ -176,7 +176,7 @@
|
||||
<!-- validation -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
</dependency>
|
||||
|
||||
@@ -230,12 +230,12 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<configuration>
|
||||
<useStandardDocletOptions>true</useStandardDocletOptions>
|
||||
<charset>${project.build.sourceEncoding}</charset>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
<docencoding>${project.build.sourceEncoding}</docencoding>
|
||||
<source>${project.build.javaLevel}</source>
|
||||
<breakiterator>true</breakiterator>
|
||||
<version>true</version>
|
||||
<author>true</author>
|
||||
@@ -253,20 +253,6 @@
|
||||
<link>http://www.slf4j.org/api/</link>
|
||||
<link>http://shiro.apache.org/static/${shiro.version}/apidocs/</link>
|
||||
</links>
|
||||
<doclet>org.jboss.apiviz.APIviz</doclet>
|
||||
<docletArtifact>
|
||||
<groupId>org.jboss.apiviz</groupId>
|
||||
<artifactId>apiviz</artifactId>
|
||||
<version>1.3.2.GA</version>
|
||||
</docletArtifact>
|
||||
<additionalOptions>
|
||||
<additionalOption>
|
||||
-sourceclasspath ${project.build.outputDirectory}
|
||||
</additionalOption>
|
||||
<additionalOption>
|
||||
-nopackagediagram
|
||||
</additionalOption>
|
||||
</additionalOptions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ package sonia.scm;
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Strings;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
@@ -57,7 +58,13 @@ public class BasicContextProvider implements SCMContextProvider
|
||||
{
|
||||
|
||||
/** Default version {@link String} */
|
||||
public static final String DEFAULT_VERSION = "unknown";
|
||||
public static final String VERSION_DEFAULT = "unknown";
|
||||
|
||||
/**
|
||||
* System property to override scm-manager version.
|
||||
* <b>Note</b>: This should only be used for testing and never in production.
|
||||
**/
|
||||
public static final String VERSION_PROPERTY = "sonia.scm.version.override";
|
||||
|
||||
/** Default name of the SCM-Manager base directory */
|
||||
public static final String DIRECTORY_DEFAULT = ".scm";
|
||||
@@ -95,7 +102,7 @@ public class BasicContextProvider implements SCMContextProvider
|
||||
try
|
||||
{
|
||||
baseDirectory = findBaseDirectory();
|
||||
version = loadVersion();
|
||||
version = determineVersion();
|
||||
stage = loadProjectStage();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -157,7 +164,7 @@ public class BasicContextProvider implements SCMContextProvider
|
||||
|
||||
/**
|
||||
* Returns the version of the SCM-Manager. If the version is not set, the
|
||||
* {@link #DEFAULT_VERSION} is returned.
|
||||
* {@link #VERSION_DEFAULT} is returned.
|
||||
*
|
||||
*
|
||||
* @return the version of the SCM-Manager
|
||||
@@ -257,6 +264,14 @@ public class BasicContextProvider implements SCMContextProvider
|
||||
return s;
|
||||
}
|
||||
|
||||
private String determineVersion() {
|
||||
String version = System.getProperty(VERSION_PROPERTY);
|
||||
if (Strings.isNullOrEmpty(version)) {
|
||||
version = loadVersion();
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the version of the SCM-Manager from maven properties file.
|
||||
*
|
||||
@@ -295,7 +310,7 @@ public class BasicContextProvider implements SCMContextProvider
|
||||
}
|
||||
}
|
||||
|
||||
return properties.getProperty(MAVEN_PROPERTY_VERSION, DEFAULT_VERSION);
|
||||
return properties.getProperty(MAVEN_PROPERTY_VERSION, VERSION_DEFAULT);
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
@@ -2,7 +2,7 @@ package sonia.scm.api.v2.resources;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
@Getter @Setter
|
||||
public class MergeCommandDto {
|
||||
|
||||
@@ -91,6 +91,7 @@ public final class RepositoryService implements Closeable {
|
||||
private final PreProcessorUtil preProcessorUtil;
|
||||
private final RepositoryServiceProvider provider;
|
||||
private final Repository repository;
|
||||
@SuppressWarnings("rawtypes")
|
||||
private final Set<ScmProtocolProvider> protocolProviders;
|
||||
private final WorkdirProvider workdirProvider;
|
||||
|
||||
@@ -104,7 +105,10 @@ public final class RepositoryService implements Closeable {
|
||||
*/
|
||||
RepositoryService(CacheManager cacheManager,
|
||||
RepositoryServiceProvider provider, Repository repository,
|
||||
PreProcessorUtil preProcessorUtil, Set<ScmProtocolProvider> protocolProviders, WorkdirProvider workdirProvider) {
|
||||
PreProcessorUtil preProcessorUtil,
|
||||
@SuppressWarnings("rawtypes") Set<ScmProtocolProvider> protocolProviders,
|
||||
WorkdirProvider workdirProvider
|
||||
) {
|
||||
this.cacheManager = cacheManager;
|
||||
this.provider = provider;
|
||||
this.repository = repository;
|
||||
@@ -445,19 +449,23 @@ public final class RepositoryService implements Closeable {
|
||||
return provider.getSupportedFeatures().contains(feature);
|
||||
}
|
||||
|
||||
public <T extends ScmProtocol> Stream<T> getSupportedProtocols() {
|
||||
public Stream<ScmProtocol> getSupportedProtocols() {
|
||||
return protocolProviders.stream()
|
||||
.filter(protocolProvider -> protocolProvider.getType().equals(getRepository().getType()))
|
||||
.map(this::<T>createProviderInstanceForRepository);
|
||||
.map(this::createProviderInstanceForRepository);
|
||||
}
|
||||
|
||||
private <T extends ScmProtocol> T createProviderInstanceForRepository(ScmProtocolProvider<T> protocolProvider) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
private ScmProtocol createProviderInstanceForRepository(ScmProtocolProvider protocolProvider) {
|
||||
return protocolProvider.get(repository);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends ScmProtocol> T getProtocol(Class<T> clazz) {
|
||||
return this.<T>getSupportedProtocols()
|
||||
return this.getSupportedProtocols()
|
||||
.filter(scmProtocol -> clazz.isAssignableFrom(scmProtocol.getClass()))
|
||||
// no idea how to fix this, without cast
|
||||
.map(p -> (T) p)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException(String.format("no implementation for %s and repository type %s", clazz.getName(),getRepository().getType())));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package sonia.scm;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junitpioneer.jupiter.TempDirectory;
|
||||
@@ -13,32 +14,59 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
@ExtendWith(TempDirectory.class)
|
||||
class BasicContextProviderTest {
|
||||
|
||||
private Path baseDirectory;
|
||||
@Nested
|
||||
class VersionTests {
|
||||
|
||||
private BasicContextProvider context;
|
||||
@Test
|
||||
void shouldReturnVersionFromSystemProperty() {
|
||||
System.setProperty(BasicContextProvider.VERSION_PROPERTY, "3.0.0");
|
||||
try {
|
||||
SCMContextProvider context = new BasicContextProvider();
|
||||
assertThat(context.getVersion()).isEqualTo("3.0.0");
|
||||
} finally {
|
||||
System.clearProperty(BasicContextProvider.VERSION_PROPERTY);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnDefaultVersion() {
|
||||
SCMContextProvider context = new BasicContextProvider();
|
||||
assertThat(context.getVersion()).isEqualTo(BasicContextProvider.VERSION_DEFAULT);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUpContext(@TempDirectory.TempDir Path baseDirectory) {
|
||||
this.baseDirectory = baseDirectory;
|
||||
context = new BasicContextProvider(baseDirectory.toFile(), "x.y.z", Stage.PRODUCTION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnAbsolutePathAsIs(@TempDirectory.TempDir Path path) {
|
||||
Path absolutePath = path.toAbsolutePath();
|
||||
Path resolved = context.resolve(absolutePath);
|
||||
@Nested
|
||||
class PathTests {
|
||||
|
||||
assertThat(resolved).isSameAs(absolutePath);
|
||||
}
|
||||
private Path baseDirectory;
|
||||
|
||||
@Test
|
||||
void shouldResolveRelatePath() {
|
||||
Path path = Paths.get("repos", "42");
|
||||
Path resolved = context.resolve(path);
|
||||
private BasicContextProvider context;
|
||||
|
||||
@BeforeEach
|
||||
void setUpContext(@TempDirectory.TempDir Path baseDirectory) {
|
||||
this.baseDirectory = baseDirectory;
|
||||
context = new BasicContextProvider(baseDirectory.toFile(), "x.y.z", Stage.PRODUCTION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnAbsolutePathAsIs(@TempDirectory.TempDir Path path) {
|
||||
Path absolutePath = path.toAbsolutePath();
|
||||
Path resolved = context.resolve(absolutePath);
|
||||
|
||||
assertThat(resolved).isSameAs(absolutePath);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveRelatePath() {
|
||||
Path path = Paths.get("repos", "42");
|
||||
Path resolved = context.resolve(path);
|
||||
|
||||
assertThat(resolved).isAbsolute();
|
||||
assertThat(resolved).startsWithRaw(baseDirectory);
|
||||
assertThat(resolved).endsWithRaw(path);
|
||||
}
|
||||
|
||||
assertThat(resolved).isAbsolute();
|
||||
assertThat(resolved).startsWithRaw(baseDirectory);
|
||||
assertThat(resolved).endsWithRaw(path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -200,6 +200,10 @@
|
||||
<name>java.awt.headless</name>
|
||||
<value>true</value>
|
||||
</systemProperty>
|
||||
<systemProperty>
|
||||
<name>sonia.scm.classloading.lifecycle</name>
|
||||
<value>simple</value>
|
||||
</systemProperty>
|
||||
</systemProperties>
|
||||
<webApp>
|
||||
<contextPath>/scm</contextPath>
|
||||
|
||||
@@ -720,6 +720,10 @@
|
||||
<name>scm.stage</name>
|
||||
<value>${scm.stage}</value>
|
||||
</systemProperty>
|
||||
<systemProperty>
|
||||
<name>sonia.scm.classloading.lifecycle</name>
|
||||
<value>simple</value>
|
||||
</systemProperty>
|
||||
</systemProperties>
|
||||
<jettyXml>${project.basedir}/src/main/conf/jetty.xml</jettyXml>
|
||||
<scanIntervalSeconds>0</scanIntervalSeconds>
|
||||
@@ -805,6 +809,10 @@
|
||||
<name>scm.home</name>
|
||||
<value>target/scm-it</value>
|
||||
</systemProperty>
|
||||
<systemProperty>
|
||||
<name>sonia.scm.classloading.lifecycle</name>
|
||||
<value>simple</value>
|
||||
</systemProperty>
|
||||
</systemProperties>
|
||||
<jettyXml>${project.basedir}/src/main/conf/jetty.xml</jettyXml>
|
||||
<scanIntervalSeconds>0</scanIntervalSeconds>
|
||||
|
||||
@@ -7,7 +7,7 @@ import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair;
|
||||
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
|
||||
import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||
import com.fasterxml.jackson.databind.util.ISO8601DateFormat;
|
||||
import com.fasterxml.jackson.databind.util.StdDateFormat;
|
||||
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
|
||||
@@ -27,7 +27,7 @@ public class ObjectMapperProvider implements Provider<ObjectMapper> {
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
|
||||
mapper.configure(SerializationFeature.WRITE_DATES_WITH_ZONE_ID, true);
|
||||
mapper.setDateFormat(new ISO8601DateFormat());
|
||||
mapper.setDateFormat(new StdDateFormat());
|
||||
return mapper;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import sonia.scm.ReducedModelObject;
|
||||
import sonia.scm.group.GroupDisplayManager;
|
||||
import sonia.scm.user.UserDisplayManager;
|
||||
|
||||
@@ -7,8 +7,7 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
@Getter @Setter @NoArgsConstructor
|
||||
|
||||
@@ -3,7 +3,7 @@ package sonia.scm.api.v2.resources;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package sonia.scm.api.v2.resources;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
|
||||
@@ -3,7 +3,7 @@ package sonia.scm.api.v2.resources;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
|
||||
@@ -7,8 +7,8 @@ import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.validator.constraints.Email;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
import javax.validation.constraints.Email;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import sonia.scm.util.ValidationUtil;
|
||||
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
@@ -6,7 +6,7 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import sonia.scm.util.ValidationUtil;
|
||||
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
@@ -6,7 +6,7 @@ import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -7,8 +7,8 @@ import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.validator.constraints.Email;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
import javax.validation.constraints.Email;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import sonia.scm.util.ValidationUtil;
|
||||
|
||||
import javax.validation.constraints.Pattern;
|
||||
|
||||
@@ -3,198 +3,75 @@ package sonia.scm.lifecycle.classloading;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor;
|
||||
import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventorFactory;
|
||||
import se.jiderhamn.classloader.leak.prevention.cleanup.IIOServiceProviderCleanUp;
|
||||
import se.jiderhamn.classloader.leak.prevention.cleanup.MBeanCleanUp;
|
||||
import se.jiderhamn.classloader.leak.prevention.cleanup.ShutdownHookCleanUp;
|
||||
import se.jiderhamn.classloader.leak.prevention.cleanup.StopThreadsCleanUp;
|
||||
import se.jiderhamn.classloader.leak.prevention.preinit.AwtToolkitInitiator;
|
||||
import se.jiderhamn.classloader.leak.prevention.preinit.Java2dDisposerInitiator;
|
||||
import se.jiderhamn.classloader.leak.prevention.preinit.Java2dRenderQueueInitiator;
|
||||
import se.jiderhamn.classloader.leak.prevention.preinit.SunAwtAppContextInitiator;
|
||||
import sonia.scm.lifecycle.LifeCycle;
|
||||
import sonia.scm.plugin.ChildFirstPluginClassLoader;
|
||||
import sonia.scm.plugin.DefaultPluginClassLoader;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static se.jiderhamn.classloader.leak.prevention.cleanup.ShutdownHookCleanUp.SHUTDOWN_HOOK_WAIT_MS_DEFAULT;
|
||||
|
||||
/**
|
||||
* Creates and shutdown SCM-Manager ClassLoaders.
|
||||
* Base class for ClassLoader LifeCycle implementation in SCM-Manager.
|
||||
*/
|
||||
public final class ClassLoaderLifeCycle implements LifeCycle {
|
||||
public abstract class ClassLoaderLifeCycle implements LifeCycle {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClassLoaderLifeCycle.class);
|
||||
|
||||
private Deque<ClassLoaderAndPreventor> classLoaders = new ArrayDeque<>();
|
||||
@VisibleForTesting
|
||||
static final String PROPERTY = "sonia.scm.classloading.lifecycle";
|
||||
|
||||
public static ClassLoaderLifeCycle create() {
|
||||
ClassLoader webappClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
String implementation = System.getProperty(PROPERTY);
|
||||
if (SimpleClassLoaderLifeCycle.NAME.equalsIgnoreCase(implementation)) {
|
||||
LOG.info("create new simple ClassLoaderLifeCycle");
|
||||
return new SimpleClassLoaderLifeCycle(webappClassLoader);
|
||||
}
|
||||
LOG.info("create new ClassLoaderLifeCycle with leak prevention");
|
||||
return new ClassLoaderLifeCycleWithLeakPrevention(webappClassLoader);
|
||||
}
|
||||
|
||||
private final ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory;
|
||||
private final ClassLoader webappClassLoader;
|
||||
|
||||
private BootstrapClassLoader bootstrapClassLoader;
|
||||
|
||||
private ClassLoaderAppendListener classLoaderAppendListener = new ClassLoaderAppendListener() {
|
||||
@Override
|
||||
public <C extends ClassLoader> C apply(C classLoader) {
|
||||
return classLoader;
|
||||
}
|
||||
};
|
||||
|
||||
@VisibleForTesting
|
||||
public static ClassLoaderLifeCycle create() {
|
||||
ClassLoader webappClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory = createClassLoaderLeakPreventorFactory(webappClassLoader);
|
||||
return new ClassLoaderLifeCycle(webappClassLoader, classLoaderLeakPreventorFactory);
|
||||
}
|
||||
|
||||
ClassLoaderLifeCycle(ClassLoader webappClassLoader, ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory) {
|
||||
this.classLoaderLeakPreventorFactory = classLoaderLeakPreventorFactory;
|
||||
this.webappClassLoader = initAndAppend(webappClassLoader);
|
||||
}
|
||||
|
||||
private static ClassLoaderLeakPreventorFactory createClassLoaderLeakPreventorFactory(ClassLoader webappClassLoader) {
|
||||
// Should threads tied to the web app classloader be forced to stop at application shutdown?
|
||||
boolean stopThreads = Boolean.getBoolean("ClassLoaderLeakPreventor.stopThreads");
|
||||
|
||||
// Should Timer threads tied to the web app classloader be forced to stop at application shutdown?
|
||||
boolean stopTimerThreads = Boolean.getBoolean("ClassLoaderLeakPreventor.stopTimerThreads");
|
||||
|
||||
// Should shutdown hooks registered from the application be executed at application shutdown?
|
||||
boolean executeShutdownHooks = Boolean.getBoolean("ClassLoaderLeakPreventor.executeShutdownHooks");
|
||||
|
||||
// No of milliseconds to wait for threads to finish execution, before stopping them.
|
||||
int threadWaitMs = Integer.getInteger("ClassLoaderLeakPreventor.threadWaitMs", ClassLoaderLeakPreventor.THREAD_WAIT_MS_DEFAULT);
|
||||
|
||||
/*
|
||||
* No of milliseconds to wait for shutdown hooks to finish execution, before stopping them.
|
||||
* If set to -1 there will be no waiting at all, but Thread is allowed to run until finished.
|
||||
*/
|
||||
int shutdownHookWaitMs = Integer.getInteger("ClassLoaderLeakPreventor.shutdownHookWaitMs", SHUTDOWN_HOOK_WAIT_MS_DEFAULT);
|
||||
|
||||
LOG.info("Settings for {} (CL: 0x{}):", ClassLoaderLifeCycle.class.getName(), Integer.toHexString(System.identityHashCode(webappClassLoader)) );
|
||||
LOG.info(" stopThreads = {}", stopThreads);
|
||||
LOG.info(" stopTimerThreads = {}", stopTimerThreads);
|
||||
LOG.info(" executeShutdownHooks = {}", executeShutdownHooks);
|
||||
LOG.info(" threadWaitMs = {} ms", threadWaitMs);
|
||||
LOG.info(" shutdownHookWaitMs = {} ms", shutdownHookWaitMs);
|
||||
|
||||
// use webapp classloader as safe base? or system?
|
||||
ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory = new ClassLoaderLeakPreventorFactory(webappClassLoader);
|
||||
classLoaderLeakPreventorFactory.setLogger(new LoggingAdapter());
|
||||
|
||||
final ShutdownHookCleanUp shutdownHookCleanUp = classLoaderLeakPreventorFactory.getCleanUp(ShutdownHookCleanUp.class);
|
||||
shutdownHookCleanUp.setExecuteShutdownHooks(executeShutdownHooks);
|
||||
shutdownHookCleanUp.setShutdownHookWaitMs(shutdownHookWaitMs);
|
||||
|
||||
final StopThreadsCleanUp stopThreadsCleanUp = classLoaderLeakPreventorFactory.getCleanUp(StopThreadsCleanUp.class);
|
||||
stopThreadsCleanUp.setStopThreads(stopThreads);
|
||||
stopThreadsCleanUp.setStopTimerThreads(stopTimerThreads);
|
||||
stopThreadsCleanUp.setThreadWaitMs(threadWaitMs);
|
||||
|
||||
// remove awt and imageio cleanup
|
||||
classLoaderLeakPreventorFactory.removePreInitiator(AwtToolkitInitiator.class);
|
||||
classLoaderLeakPreventorFactory.removePreInitiator(SunAwtAppContextInitiator.class);
|
||||
classLoaderLeakPreventorFactory.removeCleanUp(IIOServiceProviderCleanUp.class);
|
||||
classLoaderLeakPreventorFactory.removePreInitiator(Java2dRenderQueueInitiator.class);
|
||||
classLoaderLeakPreventorFactory.removePreInitiator(Java2dDisposerInitiator.class);
|
||||
|
||||
// the MBeanCleanUp causes a Exception and we use no mbeans
|
||||
classLoaderLeakPreventorFactory.removeCleanUp(MBeanCleanUp.class);
|
||||
|
||||
return classLoaderLeakPreventorFactory;
|
||||
ClassLoaderLifeCycle(ClassLoader webappClassLoader) {
|
||||
this.webappClassLoader = webappClassLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
bootstrapClassLoader = initAndAppend(new BootstrapClassLoader(webappClassLoader));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setClassLoaderAppendListener(ClassLoaderAppendListener classLoaderAppendListener) {
|
||||
this.classLoaderAppendListener = classLoaderAppendListener;
|
||||
}
|
||||
protected abstract <T extends ClassLoader> T initAndAppend(T classLoader);
|
||||
|
||||
public ClassLoader getBootstrapClassLoader() {
|
||||
checkState(bootstrapClassLoader != null, "%s was not initialized", ClassLoaderLifeCycle.class.getName());
|
||||
return bootstrapClassLoader;
|
||||
}
|
||||
|
||||
public ClassLoader createPluginClassLoader(URL[] urls, ClassLoader parent, String plugin) {
|
||||
LOG.debug("create new PluginClassLoader for {}", plugin);
|
||||
DefaultPluginClassLoader pluginClassLoader = new DefaultPluginClassLoader(urls, parent, plugin);
|
||||
return initAndAppend(pluginClassLoader);
|
||||
}
|
||||
|
||||
public ClassLoader createChildFirstPluginClassLoader(URL[] urls, ClassLoader parent, String plugin) {
|
||||
LOG.debug("create new ChildFirstPluginClassLoader for {}", plugin);
|
||||
ChildFirstPluginClassLoader pluginClassLoader = new ChildFirstPluginClassLoader(urls, parent, plugin);
|
||||
return initAndAppend(pluginClassLoader);
|
||||
}
|
||||
|
||||
public ClassLoader createPluginClassLoader(URL[] urls, ClassLoader parent, String plugin) {
|
||||
LOG.debug("create new PluginClassLoader for {}", plugin);
|
||||
DefaultPluginClassLoader pluginClassLoader = new DefaultPluginClassLoader(urls, parent, plugin);
|
||||
return initAndAppend(pluginClassLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
LOG.info("shutdown classloader infrastructure");
|
||||
ClassLoaderAndPreventor clap = classLoaders.poll();
|
||||
while (clap != null) {
|
||||
clap.shutdown();
|
||||
clap = classLoaders.poll();
|
||||
}
|
||||
// be sure it is realy empty
|
||||
classLoaders.clear();
|
||||
classLoaders = new ArrayDeque<>();
|
||||
shutdownClassLoaders();
|
||||
|
||||
bootstrapClassLoader.markAsShutdown();
|
||||
bootstrapClassLoader = null;
|
||||
}
|
||||
|
||||
private <T extends ClassLoader> T initAndAppend(T originalClassLoader) {
|
||||
LOG.debug("init classloader {}", originalClassLoader);
|
||||
T classLoader = classLoaderAppendListener.apply(originalClassLoader);
|
||||
|
||||
ClassLoaderLeakPreventor preventor = classLoaderLeakPreventorFactory.newLeakPreventor(classLoader);
|
||||
preventor.runPreClassLoaderInitiators();
|
||||
classLoaders.push(new ClassLoaderAndPreventor(classLoader, preventor));
|
||||
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
interface ClassLoaderAppendListener {
|
||||
<C extends ClassLoader> C apply(C classLoader);
|
||||
}
|
||||
|
||||
private class ClassLoaderAndPreventor {
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
private final ClassLoaderLeakPreventor preventor;
|
||||
|
||||
private ClassLoaderAndPreventor(ClassLoader classLoader, ClassLoaderLeakPreventor preventor) {
|
||||
this.classLoader = classLoader;
|
||||
this.preventor = preventor;
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
LOG.debug("shutdown classloader {}", classLoader);
|
||||
preventor.runCleanUps();
|
||||
|
||||
if (classLoader != webappClassLoader) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
private void close() {
|
||||
if (classLoader instanceof Closeable) {
|
||||
LOG.trace("close classloader {}", classLoader);
|
||||
try {
|
||||
((Closeable) classLoader).close();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("failed to close classloader", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
protected abstract void shutdownClassLoaders();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
package sonia.scm.lifecycle.classloading;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor;
|
||||
import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventorFactory;
|
||||
import se.jiderhamn.classloader.leak.prevention.cleanup.IIOServiceProviderCleanUp;
|
||||
import se.jiderhamn.classloader.leak.prevention.cleanup.MBeanCleanUp;
|
||||
import se.jiderhamn.classloader.leak.prevention.cleanup.ShutdownHookCleanUp;
|
||||
import se.jiderhamn.classloader.leak.prevention.cleanup.StopThreadsCleanUp;
|
||||
import se.jiderhamn.classloader.leak.prevention.preinit.AwtToolkitInitiator;
|
||||
import se.jiderhamn.classloader.leak.prevention.preinit.Java2dDisposerInitiator;
|
||||
import se.jiderhamn.classloader.leak.prevention.preinit.Java2dRenderQueueInitiator;
|
||||
import se.jiderhamn.classloader.leak.prevention.preinit.SunAwtAppContextInitiator;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
|
||||
import static se.jiderhamn.classloader.leak.prevention.cleanup.ShutdownHookCleanUp.SHUTDOWN_HOOK_WAIT_MS_DEFAULT;
|
||||
|
||||
/**
|
||||
* Creates and shutdown SCM-Manager ClassLoaders with ClassLoader leak detection.
|
||||
*/
|
||||
final class ClassLoaderLifeCycleWithLeakPrevention extends ClassLoaderLifeCycle {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClassLoaderLifeCycleWithLeakPrevention.class);
|
||||
|
||||
private Deque<ClassLoaderAndPreventor> classLoaders = new ArrayDeque<>();
|
||||
|
||||
private final ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory;
|
||||
|
||||
private ClassLoaderAppendListener classLoaderAppendListener = new ClassLoaderAppendListener() {
|
||||
@Override
|
||||
public <C extends ClassLoader> C apply(C classLoader) {
|
||||
return classLoader;
|
||||
}
|
||||
};
|
||||
|
||||
ClassLoaderLifeCycleWithLeakPrevention(ClassLoader webappClassLoader) {
|
||||
this(webappClassLoader, createClassLoaderLeakPreventorFactory(webappClassLoader));
|
||||
}
|
||||
|
||||
ClassLoaderLifeCycleWithLeakPrevention(ClassLoader webappClassLoader, ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory) {
|
||||
super(webappClassLoader);
|
||||
this.classLoaderLeakPreventorFactory = classLoaderLeakPreventorFactory;
|
||||
}
|
||||
|
||||
private static ClassLoaderLeakPreventorFactory createClassLoaderLeakPreventorFactory(ClassLoader webappClassLoader) {
|
||||
// Should threads tied to the web app classloader be forced to stop at application shutdown?
|
||||
boolean stopThreads = Boolean.getBoolean("ClassLoaderLeakPreventor.stopThreads");
|
||||
|
||||
// Should Timer threads tied to the web app classloader be forced to stop at application shutdown?
|
||||
boolean stopTimerThreads = Boolean.getBoolean("ClassLoaderLeakPreventor.stopTimerThreads");
|
||||
|
||||
// Should shutdown hooks registered from the application be executed at application shutdown?
|
||||
boolean executeShutdownHooks = Boolean.getBoolean("ClassLoaderLeakPreventor.executeShutdownHooks");
|
||||
|
||||
// No of milliseconds to wait for threads to finish execution, before stopping them.
|
||||
int threadWaitMs = Integer.getInteger("ClassLoaderLeakPreventor.threadWaitMs", ClassLoaderLeakPreventor.THREAD_WAIT_MS_DEFAULT);
|
||||
|
||||
/*
|
||||
* No of milliseconds to wait for shutdown hooks to finish execution, before stopping them.
|
||||
* If set to -1 there will be no waiting at all, but Thread is allowed to run until finished.
|
||||
*/
|
||||
int shutdownHookWaitMs = Integer.getInteger("ClassLoaderLeakPreventor.shutdownHookWaitMs", SHUTDOWN_HOOK_WAIT_MS_DEFAULT);
|
||||
|
||||
LOG.info("Settings for {} (CL: 0x{}):", ClassLoaderLifeCycleWithLeakPrevention.class.getName(), Integer.toHexString(System.identityHashCode(webappClassLoader)) );
|
||||
LOG.info(" stopThreads = {}", stopThreads);
|
||||
LOG.info(" stopTimerThreads = {}", stopTimerThreads);
|
||||
LOG.info(" executeShutdownHooks = {}", executeShutdownHooks);
|
||||
LOG.info(" threadWaitMs = {} ms", threadWaitMs);
|
||||
LOG.info(" shutdownHookWaitMs = {} ms", shutdownHookWaitMs);
|
||||
|
||||
// use webapp classloader as safe base? or system?
|
||||
ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory = new ClassLoaderLeakPreventorFactory(webappClassLoader);
|
||||
classLoaderLeakPreventorFactory.setLogger(new LoggingAdapter());
|
||||
|
||||
final ShutdownHookCleanUp shutdownHookCleanUp = classLoaderLeakPreventorFactory.getCleanUp(ShutdownHookCleanUp.class);
|
||||
shutdownHookCleanUp.setExecuteShutdownHooks(executeShutdownHooks);
|
||||
shutdownHookCleanUp.setShutdownHookWaitMs(shutdownHookWaitMs);
|
||||
|
||||
final StopThreadsCleanUp stopThreadsCleanUp = classLoaderLeakPreventorFactory.getCleanUp(StopThreadsCleanUp.class);
|
||||
stopThreadsCleanUp.setStopThreads(stopThreads);
|
||||
stopThreadsCleanUp.setStopTimerThreads(stopTimerThreads);
|
||||
stopThreadsCleanUp.setThreadWaitMs(threadWaitMs);
|
||||
|
||||
// remove awt and imageio cleanup
|
||||
classLoaderLeakPreventorFactory.removePreInitiator(AwtToolkitInitiator.class);
|
||||
classLoaderLeakPreventorFactory.removePreInitiator(SunAwtAppContextInitiator.class);
|
||||
classLoaderLeakPreventorFactory.removeCleanUp(IIOServiceProviderCleanUp.class);
|
||||
classLoaderLeakPreventorFactory.removePreInitiator(Java2dRenderQueueInitiator.class);
|
||||
classLoaderLeakPreventorFactory.removePreInitiator(Java2dDisposerInitiator.class);
|
||||
|
||||
// the MBeanCleanUp causes a Exception and we use no mbeans
|
||||
classLoaderLeakPreventorFactory.removeCleanUp(MBeanCleanUp.class);
|
||||
|
||||
return classLoaderLeakPreventorFactory;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setClassLoaderAppendListener(ClassLoaderAppendListener classLoaderAppendListener) {
|
||||
this.classLoaderAppendListener = classLoaderAppendListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutdownClassLoaders() {
|
||||
ClassLoaderAndPreventor clap = classLoaders.poll();
|
||||
while (clap != null) {
|
||||
clap.shutdown();
|
||||
clap = classLoaders.poll();
|
||||
}
|
||||
// be sure it is realy empty
|
||||
classLoaders.clear();
|
||||
classLoaders = new ArrayDeque<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T extends ClassLoader> T initAndAppend(T originalClassLoader) {
|
||||
LOG.debug("init classloader {}", originalClassLoader);
|
||||
T classLoader = classLoaderAppendListener.apply(originalClassLoader);
|
||||
|
||||
ClassLoaderLeakPreventor preventor = classLoaderLeakPreventorFactory.newLeakPreventor(classLoader);
|
||||
preventor.runPreClassLoaderInitiators();
|
||||
classLoaders.push(new ClassLoaderAndPreventor(classLoader, preventor));
|
||||
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
interface ClassLoaderAppendListener {
|
||||
<C extends ClassLoader> C apply(C classLoader);
|
||||
}
|
||||
|
||||
private static class ClassLoaderAndPreventor {
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
private final ClassLoaderLeakPreventor preventor;
|
||||
|
||||
private ClassLoaderAndPreventor(ClassLoader classLoader, ClassLoaderLeakPreventor preventor) {
|
||||
this.classLoader = classLoader;
|
||||
this.preventor = preventor;
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
LOG.debug("shutdown classloader {}", classLoader);
|
||||
preventor.runCleanUps();
|
||||
close();
|
||||
}
|
||||
|
||||
private void close() {
|
||||
if (classLoader instanceof Closeable) {
|
||||
LOG.trace("close classloader {}", classLoader);
|
||||
try {
|
||||
((Closeable) classLoader).close();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("failed to close classloader", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package sonia.scm.lifecycle.classloading;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
|
||||
/**
|
||||
* Creates and shutdown SCM-Manager ClassLoaders without ClassLoader leak detection.
|
||||
*/
|
||||
class SimpleClassLoaderLifeCycle extends ClassLoaderLifeCycle {
|
||||
|
||||
static final String NAME = "simple";
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SimpleClassLoaderLifeCycle.class);
|
||||
|
||||
private Deque<ClassLoader> classLoaders = new ArrayDeque<>();
|
||||
|
||||
SimpleClassLoaderLifeCycle(ClassLoader webappClassLoader) {
|
||||
super(webappClassLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T extends ClassLoader> T initAndAppend(T classLoader) {
|
||||
LOG.debug("init classloader {}", classLoader);
|
||||
classLoaders.push(classLoader);
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void shutdownClassLoaders() {
|
||||
ClassLoader classLoader = classLoaders.poll();
|
||||
while (classLoader != null) {
|
||||
shutdown(classLoader);
|
||||
classLoader = classLoaders.poll();
|
||||
}
|
||||
// be sure it is realy empty
|
||||
classLoaders.clear();
|
||||
classLoaders = new ArrayDeque<>();
|
||||
}
|
||||
|
||||
private void shutdown(ClassLoader classLoader) {
|
||||
LOG.debug("shutdown classloader {}", classLoader);
|
||||
if (classLoader instanceof Closeable) {
|
||||
LOG.trace("close classloader {}", classLoader);
|
||||
try {
|
||||
((Closeable) classLoader).close();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("failed to close classloader", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package sonia.scm.api.rest;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ObjectMapperProviderTest {
|
||||
|
||||
private final ObjectMapperProvider provider = new ObjectMapperProvider();
|
||||
|
||||
@Test
|
||||
void shouldFormatInstantAsISO8601() throws JsonProcessingException {
|
||||
ZoneId zone = ZoneId.of("Europe/Berlin");
|
||||
ZonedDateTime date = ZonedDateTime.of(2020, 2, 4, 15, 21, 42, 0, zone);
|
||||
|
||||
String value = provider.get().writeValueAsString(date.toInstant());
|
||||
assertThat(value).isEqualTo("\"2020-02-04T14:21:42Z\"");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,116 +1,25 @@
|
||||
package sonia.scm.lifecycle.classloading;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor;
|
||||
import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventorFactory;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ClassLoaderLifeCycleTest {
|
||||
|
||||
@Mock
|
||||
private ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory;
|
||||
|
||||
@Mock
|
||||
private ClassLoaderLeakPreventor classLoaderLeakPreventor;
|
||||
|
||||
@Test
|
||||
void shouldThrowIllegalStateExceptionWithoutInit() {
|
||||
ClassLoaderLifeCycle lifeCycle = ClassLoaderLifeCycle.create();
|
||||
assertThrows(IllegalStateException.class, lifeCycle::getBootstrapClassLoader);
|
||||
void shouldCreateSimpleClassLoader() {
|
||||
System.setProperty(ClassLoaderLifeCycle.PROPERTY, SimpleClassLoaderLifeCycle.NAME);
|
||||
try {
|
||||
ClassLoaderLifeCycle classLoaderLifeCycle = ClassLoaderLifeCycle.create();
|
||||
assertThat(classLoaderLifeCycle).isInstanceOf(SimpleClassLoaderLifeCycle.class);
|
||||
} finally {
|
||||
System.clearProperty(ClassLoaderLifeCycle.PROPERTY);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowIllegalStateExceptionAfterShutdown() {
|
||||
ClassLoaderLifeCycle lifeCycle = createMockedLifeCycle();
|
||||
lifeCycle.initialize();
|
||||
|
||||
lifeCycle.shutdown();
|
||||
assertThrows(IllegalStateException.class, lifeCycle::getBootstrapClassLoader);
|
||||
void shouldCreateDefaultClassLoader() {
|
||||
ClassLoaderLifeCycle classLoaderLifeCycle = ClassLoaderLifeCycle.create();
|
||||
assertThat(classLoaderLifeCycle).isInstanceOf(ClassLoaderLifeCycleWithLeakPrevention.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateBootstrapClassLoaderOnInit() {
|
||||
ClassLoaderLifeCycle lifeCycle = ClassLoaderLifeCycle.create();
|
||||
lifeCycle.initialize();
|
||||
|
||||
assertThat(lifeCycle.getBootstrapClassLoader()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCallTheLeakPreventor() {
|
||||
ClassLoaderLifeCycle lifeCycle = createMockedLifeCycle();
|
||||
|
||||
lifeCycle.initialize();
|
||||
verify(classLoaderLeakPreventor, times(2)).runPreClassLoaderInitiators();
|
||||
|
||||
lifeCycle.createChildFirstPluginClassLoader(new URL[0], null, "a");
|
||||
lifeCycle.createPluginClassLoader(new URL[0], null, "b");
|
||||
verify(classLoaderLeakPreventor, times(4)).runPreClassLoaderInitiators();
|
||||
|
||||
lifeCycle.shutdown();
|
||||
verify(classLoaderLeakPreventor, times(4)).runCleanUps();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCloseCloseableClassLoaders() throws IOException {
|
||||
// we use URLClassLoader, because we must be sure that the classloader is closable
|
||||
URLClassLoader webappClassLoader = spy(new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()));
|
||||
|
||||
ClassLoaderLifeCycle lifeCycle = createMockedLifeCycle(webappClassLoader);
|
||||
lifeCycle.setClassLoaderAppendListener(new ClassLoaderLifeCycle.ClassLoaderAppendListener() {
|
||||
@Override
|
||||
public <C extends ClassLoader> C apply(C classLoader) {
|
||||
return spy(classLoader);
|
||||
}
|
||||
});
|
||||
lifeCycle.initialize();
|
||||
|
||||
ClassLoader pluginA = lifeCycle.createChildFirstPluginClassLoader(new URL[0], null, "a");
|
||||
ClassLoader pluginB = lifeCycle.createPluginClassLoader(new URL[0], null, "b");
|
||||
|
||||
lifeCycle.shutdown();
|
||||
|
||||
closed(pluginB);
|
||||
closed(pluginA);
|
||||
|
||||
neverClosed(webappClassLoader);
|
||||
}
|
||||
|
||||
private void neverClosed(Object object) throws IOException {
|
||||
Closeable closeable = closeable(object);
|
||||
verify(closeable, never()).close();
|
||||
}
|
||||
|
||||
private void closed(Object object) throws IOException {
|
||||
Closeable closeable = closeable(object);
|
||||
verify(closeable).close();
|
||||
}
|
||||
|
||||
private Closeable closeable(Object object) {
|
||||
assertThat(object).isInstanceOf(Closeable.class);
|
||||
return (Closeable) object;
|
||||
}
|
||||
|
||||
private ClassLoaderLifeCycle createMockedLifeCycle() {
|
||||
return createMockedLifeCycle(Thread.currentThread().getContextClassLoader());
|
||||
}
|
||||
|
||||
private ClassLoaderLifeCycle createMockedLifeCycle(ClassLoader classLoader) {
|
||||
when(classLoaderLeakPreventorFactory.newLeakPreventor(any(ClassLoader.class))).thenReturn(classLoaderLeakPreventor);
|
||||
return new ClassLoaderLifeCycle(classLoader, classLoaderLeakPreventorFactory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package sonia.scm.lifecycle.classloading;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor;
|
||||
import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventorFactory;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ClassLoaderLifeCycleWithLeakPreventionTest {
|
||||
|
||||
@Mock
|
||||
private ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory;
|
||||
|
||||
@Mock
|
||||
private ClassLoaderLeakPreventor classLoaderLeakPreventor;
|
||||
|
||||
@Test
|
||||
void shouldThrowIllegalStateExceptionWithoutInit() {
|
||||
ClassLoaderLifeCycleWithLeakPrevention lifeCycle = new ClassLoaderLifeCycleWithLeakPrevention(Thread.currentThread().getContextClassLoader());
|
||||
assertThrows(IllegalStateException.class, lifeCycle::getBootstrapClassLoader);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowIllegalStateExceptionAfterShutdown() {
|
||||
ClassLoaderLifeCycleWithLeakPrevention lifeCycle = createMockedLifeCycle();
|
||||
lifeCycle.initialize();
|
||||
|
||||
lifeCycle.shutdown();
|
||||
assertThrows(IllegalStateException.class, lifeCycle::getBootstrapClassLoader);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateBootstrapClassLoaderOnInit() {
|
||||
ClassLoaderLifeCycleWithLeakPrevention lifeCycle = new ClassLoaderLifeCycleWithLeakPrevention(Thread.currentThread().getContextClassLoader());
|
||||
lifeCycle.initialize();
|
||||
|
||||
assertThat(lifeCycle.getBootstrapClassLoader()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCallTheLeakPreventor() {
|
||||
ClassLoaderLifeCycleWithLeakPrevention lifeCycle = createMockedLifeCycle();
|
||||
|
||||
lifeCycle.initialize();
|
||||
verify(classLoaderLeakPreventor, times(1)).runPreClassLoaderInitiators();
|
||||
|
||||
lifeCycle.createChildFirstPluginClassLoader(new URL[0], null, "a");
|
||||
lifeCycle.createPluginClassLoader(new URL[0], null, "b");
|
||||
verify(classLoaderLeakPreventor, times(3)).runPreClassLoaderInitiators();
|
||||
|
||||
lifeCycle.shutdown();
|
||||
verify(classLoaderLeakPreventor, times(3)).runCleanUps();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCloseCloseableClassLoaders() throws IOException {
|
||||
// we use URLClassLoader, because we must be sure that the classloader is closable
|
||||
URLClassLoader webappClassLoader = spy(new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader()));
|
||||
|
||||
ClassLoaderLifeCycleWithLeakPrevention lifeCycle = createMockedLifeCycle(webappClassLoader);
|
||||
lifeCycle.setClassLoaderAppendListener(new ClassLoaderLifeCycleWithLeakPrevention.ClassLoaderAppendListener() {
|
||||
@Override
|
||||
public <C extends ClassLoader> C apply(C classLoader) {
|
||||
return spy(classLoader);
|
||||
}
|
||||
});
|
||||
lifeCycle.initialize();
|
||||
|
||||
ClassLoader pluginA = lifeCycle.createChildFirstPluginClassLoader(new URL[0], null, "a");
|
||||
ClassLoader pluginB = lifeCycle.createPluginClassLoader(new URL[0], null, "b");
|
||||
|
||||
lifeCycle.shutdown();
|
||||
|
||||
closed(pluginB);
|
||||
closed(pluginA);
|
||||
|
||||
neverClosed(webappClassLoader);
|
||||
}
|
||||
|
||||
private void neverClosed(Object object) throws IOException {
|
||||
Closeable closeable = closeable(object);
|
||||
verify(closeable, never()).close();
|
||||
}
|
||||
|
||||
private void closed(Object object) throws IOException {
|
||||
Closeable closeable = closeable(object);
|
||||
verify(closeable).close();
|
||||
}
|
||||
|
||||
private Closeable closeable(Object object) {
|
||||
assertThat(object).isInstanceOf(Closeable.class);
|
||||
return (Closeable) object;
|
||||
}
|
||||
|
||||
private ClassLoaderLifeCycleWithLeakPrevention createMockedLifeCycle() {
|
||||
return createMockedLifeCycle(Thread.currentThread().getContextClassLoader());
|
||||
}
|
||||
|
||||
private ClassLoaderLifeCycleWithLeakPrevention createMockedLifeCycle(ClassLoader classLoader) {
|
||||
when(classLoaderLeakPreventorFactory.newLeakPreventor(any(ClassLoader.class))).thenReturn(classLoaderLeakPreventor);
|
||||
return new ClassLoaderLifeCycleWithLeakPrevention(classLoader, classLoaderLeakPreventorFactory);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package sonia.scm.lifecycle.classloading;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class SimpleClassLoaderLifeCycleTest {
|
||||
|
||||
@Test
|
||||
void shouldCloseClosableClassLoaderOnShutdown() {
|
||||
SimpleClassLoaderLifeCycle lifeCycle = new SimpleClassLoaderLifeCycle(Thread.currentThread().getContextClassLoader());
|
||||
lifeCycle.initialize();
|
||||
|
||||
ClosableClassLoader classLoader = new ClosableClassLoader();
|
||||
lifeCycle.initAndAppend(classLoader);
|
||||
|
||||
lifeCycle.shutdown();
|
||||
|
||||
assertThat(classLoader.closed).isTrue();
|
||||
}
|
||||
|
||||
private static class ClosableClassLoader extends ClassLoader implements Closeable {
|
||||
|
||||
private boolean closed = false;
|
||||
|
||||
public ClosableClassLoader() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user