diff --git a/CHANGELOG.md b/CHANGELOG.md
index 462b378436..9a4de014bd 100644
--- a/CHANGELOG.md
+++ b/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
diff --git a/Jenkinsfile b/Jenkinsfile
index 6ad4f7d459..43fece6567 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -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
diff --git a/lombok.config b/lombok.config
new file mode 100644
index 0000000000..d8ef4d1d98
--- /dev/null
+++ b/lombok.config
@@ -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
diff --git a/pom.xml b/pom.xml
index a9e43c9015..48e94c11de 100644
--- a/pom.xml
+++ b/pom.xml
@@ -212,7 +212,7 @@
org.projectlombok
lombok
- 1.16.18
+ 1.18.10
provided
@@ -285,9 +285,9 @@
- org.hibernate
+ org.hibernate.validator
hibernate-validator
- ${hibernate-validator.version}
+ ${hibernate-validator.version}
@@ -434,7 +434,7 @@
org.apache.maven.plugins
maven-javadoc-plugin
- 3.0.1
+ 3.1.1
org.apache.maven.plugins
@@ -456,7 +456,7 @@
sonia.scm.maven
smp-maven-plugin
- 1.0.0-rc2
+ 1.0.0-rc3
@@ -625,6 +625,7 @@
${project.build.sourceEncoding}
${project.build.sourceEncoding}
true
+ ${project.build.javaLevel}
true
true
true
@@ -664,11 +665,11 @@
org.apache.maven.plugins
maven-release-plugin
- 2.3.2
+ 2.5.3
false
true
- release,APIviz,doc,nativepkg
+ release,doc,nativepkg
@{project.version}
true
@@ -798,32 +799,6 @@
-
- APIviz
-
-
- sonia.apiviz
- true
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
- 3.0.0
-
- false
-
-
-
-
-
-
-
-
@@ -844,7 +819,7 @@
- 2.23.0
+ 2.28.2
1.3
5.2.0
@@ -863,7 +838,7 @@
6.1.0.Final
- 1.6.1
+ 1.6.2
9.4.22.v20191022
@@ -885,8 +860,8 @@
1.16.0
- 1.8
- 1.8
+ 8
+ 8
UTF-8
SCM-BSD
diff --git a/scm-annotations/src/main/java/sonia/scm/i18n/I18n.java b/scm-annotations/src/main/java/sonia/scm/i18n/I18n.java
index 63d761fb17..5f3f6cd18f 100644
--- a/scm-annotations/src/main/java/sonia/scm/i18n/I18n.java
+++ b/scm-annotations/src/main/java/sonia/scm/i18n/I18n.java
@@ -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();
diff --git a/scm-core/pom.xml b/scm-core/pom.xml
index 086013364b..563ac0f40f 100644
--- a/scm-core/pom.xml
+++ b/scm-core/pom.xml
@@ -1,6 +1,6 @@
-
+
4.0.0
@@ -15,7 +15,7 @@
scm-core
-
+
@@ -55,13 +55,13 @@
-
+
org.apache.shiro
shiro-core
${shiro.version}
-
+
@@ -176,7 +176,7 @@
- org.hibernate
+ org.hibernate.validator
hibernate-validator
@@ -230,12 +230,12 @@
org.apache.maven.plugins
maven-javadoc-plugin
- 3.0.0
true
${project.build.sourceEncoding}
${project.build.sourceEncoding}
${project.build.sourceEncoding}
+ ${project.build.javaLevel}
true
true
true
@@ -253,20 +253,6 @@
http://www.slf4j.org/api/
http://shiro.apache.org/static/${shiro.version}/apidocs/
- org.jboss.apiviz.APIviz
-
- org.jboss.apiviz
- apiviz
- 1.3.2.GA
-
-
-
- -sourceclasspath ${project.build.outputDirectory}
-
-
- -nopackagediagram
-
-
diff --git a/scm-core/src/main/java/sonia/scm/BasicContextProvider.java b/scm-core/src/main/java/sonia/scm/BasicContextProvider.java
index f4ed04c3f5..1764aceba8 100644
--- a/scm-core/src/main/java/sonia/scm/BasicContextProvider.java
+++ b/scm-core/src/main/java/sonia/scm/BasicContextProvider.java
@@ -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.
+ * Note: 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 ----------------------------------------------------------
diff --git a/scm-core/src/main/java/sonia/scm/api/v2/resources/MergeCommandDto.java b/scm-core/src/main/java/sonia/scm/api/v2/resources/MergeCommandDto.java
index 0661d6a4ef..dfadc08b3e 100644
--- a/scm-core/src/main/java/sonia/scm/api/v2/resources/MergeCommandDto.java
+++ b/scm-core/src/main/java/sonia/scm/api/v2/resources/MergeCommandDto.java
@@ -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 {
diff --git a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java
index 080d66ebf2..586f28761b 100644
--- a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java
+++ b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryService.java
@@ -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 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 protocolProviders, WorkdirProvider workdirProvider) {
+ PreProcessorUtil preProcessorUtil,
+ @SuppressWarnings("rawtypes") Set 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 Stream getSupportedProtocols() {
+ public Stream getSupportedProtocols() {
return protocolProviders.stream()
.filter(protocolProvider -> protocolProvider.getType().equals(getRepository().getType()))
- .map(this::createProviderInstanceForRepository);
+ .map(this::createProviderInstanceForRepository);
}
- private T createProviderInstanceForRepository(ScmProtocolProvider protocolProvider) {
+ @SuppressWarnings("rawtypes")
+ private ScmProtocol createProviderInstanceForRepository(ScmProtocolProvider protocolProvider) {
return protocolProvider.get(repository);
}
+ @SuppressWarnings("unchecked")
public T getProtocol(Class clazz) {
- return this.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())));
}
diff --git a/scm-core/src/test/java/sonia/scm/BasicContextProviderTest.java b/scm-core/src/test/java/sonia/scm/BasicContextProviderTest.java
index 4fb9dfd4fa..523e579b17 100644
--- a/scm-core/src/test/java/sonia/scm/BasicContextProviderTest.java
+++ b/scm-core/src/test/java/sonia/scm/BasicContextProviderTest.java
@@ -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);
}
}
diff --git a/scm-it/pom.xml b/scm-it/pom.xml
index c38c73ce3e..9064a39fc1 100644
--- a/scm-it/pom.xml
+++ b/scm-it/pom.xml
@@ -200,6 +200,10 @@
java.awt.headless
true
+
+ sonia.scm.classloading.lifecycle
+ simple
+
/scm
diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml
index 487eb0bfeb..924e176024 100644
--- a/scm-webapp/pom.xml
+++ b/scm-webapp/pom.xml
@@ -720,6 +720,10 @@
scm.stage
${scm.stage}
+
+ sonia.scm.classloading.lifecycle
+ simple
+
${project.basedir}/src/main/conf/jetty.xml
0
@@ -805,6 +809,10 @@
scm.home
target/scm-it
+
+ sonia.scm.classloading.lifecycle
+ simple
+
${project.basedir}/src/main/conf/jetty.xml
0
diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/ObjectMapperProvider.java b/scm-webapp/src/main/java/sonia/scm/api/rest/ObjectMapperProvider.java
index 68522234d3..7a59ba1339 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/rest/ObjectMapperProvider.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/rest/ObjectMapperProvider.java
@@ -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 {
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;
}
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AutoCompleteResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AutoCompleteResource.java
index 38b421f573..26868e32f5 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AutoCompleteResource.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/AutoCompleteResource.java
@@ -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;
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDto.java
index c66428697d..f32b93f813 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDto.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchDto.java
@@ -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
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRequestDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRequestDto.java
index 3db338ea85..02b9e37ade 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRequestDto.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/BranchRequestDto.java
@@ -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;
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PasswordChangeDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PasswordChangeDto.java
index 8a69c58e86..d3e96c7f02 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PasswordChangeDto.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PasswordChangeDto.java
@@ -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
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PasswordOverwriteDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PasswordOverwriteDto.java
index 0570ed81e1..21b5c0bec6 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PasswordOverwriteDto.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/PasswordOverwriteDto.java
@@ -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
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDto.java
index f3f28191ae..df0fb0bafc 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDto.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDto.java
@@ -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;
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionDto.java
index 398e219207..37d2dce537 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionDto.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryPermissionDto.java
@@ -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;
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleDto.java
index 7840cad0ee..c6c5f2a779 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleDto.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryRoleDto.java
@@ -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;
diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserDto.java
index a46f353be4..d8a9885398 100644
--- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserDto.java
+++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserDto.java
@@ -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;
diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycle.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycle.java
index a64ed6fa43..fb7d991c1e 100644
--- a/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycle.java
+++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycle.java
@@ -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 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 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 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 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 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();
}
diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleWithLeakPrevention.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleWithLeakPrevention.java
new file mode 100644
index 0000000000..e2e7a032b7
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleWithLeakPrevention.java
@@ -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 classLoaders = new ArrayDeque<>();
+
+ private final ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory;
+
+ private ClassLoaderAppendListener classLoaderAppendListener = new ClassLoaderAppendListener() {
+ @Override
+ public 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 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 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);
+ }
+ }
+ }
+ }
+}
diff --git a/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/SimpleClassLoaderLifeCycle.java b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/SimpleClassLoaderLifeCycle.java
new file mode 100644
index 0000000000..9ea80e0592
--- /dev/null
+++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/SimpleClassLoaderLifeCycle.java
@@ -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 classLoaders = new ArrayDeque<>();
+
+ SimpleClassLoaderLifeCycle(ClassLoader webappClassLoader) {
+ super(webappClassLoader);
+ }
+
+ @Override
+ protected 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);
+ }
+ }
+ }
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/api/rest/ObjectMapperProviderTest.java b/scm-webapp/src/test/java/sonia/scm/api/rest/ObjectMapperProviderTest.java
new file mode 100644
index 0000000000..0676961d06
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/api/rest/ObjectMapperProviderTest.java
@@ -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\"");
+ }
+
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleTest.java
index a8f37777d7..619b09940e 100644
--- a/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleTest.java
+++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleTest.java
@@ -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 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);
- }
-
}
diff --git a/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleWithLeakPreventionTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleWithLeakPreventionTest.java
new file mode 100644
index 0000000000..9c76fbfd00
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleWithLeakPreventionTest.java
@@ -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 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);
+ }
+
+}
diff --git a/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/SimpleClassLoaderLifeCycleTest.java b/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/SimpleClassLoaderLifeCycleTest.java
new file mode 100644
index 0000000000..a40d0fb353
--- /dev/null
+++ b/scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/SimpleClassLoaderLifeCycleTest.java
@@ -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;
+ }
+ }
+}