From 6fe8053639adaa3d1659e57f0bbc48b8ed40194c Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 3 Feb 2020 13:51:21 +0100 Subject: [PATCH 01/14] upgrade legman to v1.6.2 in order to fix execution on Java versions > 8 --- CHANGELOG.md | 4 ++++ pom.xml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 462b378436..cb064ac79d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ 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 +### Changed +- Upgrade [Legman](https://github.com/sdorra/legman) to v1.6.2 in order to fix execution on Java versions > 8 + ## 2.0.0-rc3 - 2020-01-31 ### Fixed - Broken plugin order fixed diff --git a/pom.xml b/pom.xml index a9e43c9015..eb98edf7ce 100644 --- a/pom.xml +++ b/pom.xml @@ -863,7 +863,7 @@ 6.1.0.Final - 1.6.1 + 1.6.2 9.4.22.v20191022 From 0173862127b5c33c11b99caff4ef0bd4aa136900 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 3 Feb 2020 14:53:26 +0100 Subject: [PATCH 02/14] Upgrade Lombok to v1.18.10 in order to fix build on Java versions > 8 --- CHANGELOG.md | 1 + lombok.config | 4 ++++ pom.xml | 4 ++-- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 lombok.config diff --git a/CHANGELOG.md b/CHANGELOG.md index cb064ac79d..2a1d61e10b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### 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 ## 2.0.0-rc3 - 2020-01-31 ### Fixed 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 eb98edf7ce..0bba74026b 100644 --- a/pom.xml +++ b/pom.xml @@ -212,7 +212,7 @@ org.projectlombok lombok - 1.16.18 + 1.18.10 provided @@ -287,7 +287,7 @@ org.hibernate hibernate-validator - ${hibernate-validator.version} + ${hibernate-validator.version} From 73c3d29820194e2ea64a189173ac69269a6e2b32 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 3 Feb 2020 16:10:41 +0100 Subject: [PATCH 03/14] upgrade mockitoto v2.28.2 in order to fix tests on Java versions > 8 --- CHANGELOG.md | 3 ++- pom.xml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a1d61e10b..edbf32eb6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### 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 [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 ## 2.0.0-rc3 - 2020-01-31 ### Fixed diff --git a/pom.xml b/pom.xml index 0bba74026b..ddd07a432e 100644 --- a/pom.xml +++ b/pom.xml @@ -844,7 +844,7 @@ - 2.23.0 + 2.28.2 1.3 5.2.0 From d14acf2f59b2bcdd19d4819ceea68bc83d724cf9 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 3 Feb 2020 16:43:06 +0100 Subject: [PATCH 04/14] fix copy of core plugins on Java > 8 --- CHANGELOG.md | 1 + pom.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edbf32eb6e..97d849a4e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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 diff --git a/pom.xml b/pom.xml index ddd07a432e..24f7c9403d 100644 --- a/pom.xml +++ b/pom.xml @@ -456,7 +456,7 @@ sonia.scm.maven smp-maven-plugin - 1.0.0-rc2 + 1.0.0-rc3 From 95dc2b02d7f5c06a16d6e8d82ed4b4338fedcd5f Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 3 Feb 2020 16:43:36 +0100 Subject: [PATCH 05/14] fixed build on Java > 8 --- CHANGELOG.md | 3 +++ .../scm/repository/api/RepositoryService.java | 18 +++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97d849a4e5..aa519112bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ 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 + ### 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 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()))); } From 00b5471ca7847ba2951a017d146d229392b942d3 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 4 Feb 2020 09:56:43 +0100 Subject: [PATCH 06/14] fix some compiler warnings around hibernate-validator --- pom.xml | 2 +- scm-core/pom.xml | 2 +- .../main/java/sonia/scm/api/v2/resources/MergeCommandDto.java | 2 +- .../java/sonia/scm/api/v2/resources/AutoCompleteResource.java | 2 +- .../src/main/java/sonia/scm/api/v2/resources/BranchDto.java | 3 +-- .../java/sonia/scm/api/v2/resources/BranchRequestDto.java | 2 +- .../java/sonia/scm/api/v2/resources/PasswordChangeDto.java | 2 +- .../java/sonia/scm/api/v2/resources/PasswordOverwriteDto.java | 2 +- .../main/java/sonia/scm/api/v2/resources/RepositoryDto.java | 4 ++-- .../sonia/scm/api/v2/resources/RepositoryPermissionDto.java | 2 +- .../java/sonia/scm/api/v2/resources/RepositoryRoleDto.java | 2 +- .../src/main/java/sonia/scm/api/v2/resources/UserDto.java | 4 ++-- 12 files changed, 14 insertions(+), 15 deletions(-) diff --git a/pom.xml b/pom.xml index 24f7c9403d..68735f4018 100644 --- a/pom.xml +++ b/pom.xml @@ -285,7 +285,7 @@ - org.hibernate + org.hibernate.validator hibernate-validator ${hibernate-validator.version} diff --git a/scm-core/pom.xml b/scm-core/pom.xml index 086013364b..36e0cb023e 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -176,7 +176,7 @@ - org.hibernate + org.hibernate.validator hibernate-validator 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-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; From a36551597dd0b24d5d61ca23a94b7f077267718b Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 4 Feb 2020 15:27:30 +0100 Subject: [PATCH 07/14] fix deprecation warning of Jackson ISO8601DateFormat --- .../scm/api/rest/ObjectMapperProvider.java | 4 ++-- .../api/rest/ObjectMapperProviderTest.java | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 scm-webapp/src/test/java/sonia/scm/api/rest/ObjectMapperProviderTest.java 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/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\""); + } + +} From 71c5f68878b0114cd67373fa97d1eeeee3270962 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 4 Feb 2020 15:59:11 +0100 Subject: [PATCH 08/14] implemented simple ClassLoaderLifeCycle to fix integration tests on Java > 8 --- CHANGELOG.md | 1 + scm-it/pom.xml | 4 + scm-webapp/pom.xml | 8 + .../classloading/ClassLoaderLifeCycle.java | 179 +++--------------- ...lassLoaderLifeCycleWithLeakPrevention.java | 163 ++++++++++++++++ .../SimpleClassLoaderLifeCycle.java | 56 ++++++ .../ClassLoaderLifeCycleTest.java | 113 ++--------- ...LoaderLifeCycleWithLeakPreventionTest.java | 116 ++++++++++++ .../SimpleClassLoaderLifeCycleTest.java | 37 ++++ 9 files changed, 424 insertions(+), 253 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleWithLeakPrevention.java create mode 100644 scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/SimpleClassLoaderLifeCycle.java create mode 100644 scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/ClassLoaderLifeCycleWithLeakPreventionTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/lifecycle/classloading/SimpleClassLoaderLifeCycleTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index aa519112bd..9a4de014bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 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 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/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..3c18c10680 --- /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 with 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/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; + } + } +} From 7f471168b6d88e009c2cf4189894b47558166c3f Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 4 Feb 2020 16:07:24 +0100 Subject: [PATCH 09/14] use jdk-11 for ci builds --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 6ad4f7d459..839f9ce3b0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -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 From 53dc34f7b4d756dab98d3eed6889bf98d088dff4 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 5 Feb 2020 11:04:18 +0100 Subject: [PATCH 10/14] allow to override scm version to simplify testing --- .../java/sonia/scm/BasicContextProvider.java | 23 +++++-- .../sonia/scm/BasicContextProviderTest.java | 66 +++++++++++++------ 2 files changed, 66 insertions(+), 23 deletions(-) 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/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); } } From ab169670f06cdad16efbb6ef48e1934407d7b64f Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Thu, 6 Feb 2020 09:58:49 +0100 Subject: [PATCH 11/14] Fix JavaDoc --- .../scm/lifecycle/classloading/SimpleClassLoaderLifeCycle.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 3c18c10680..9ea80e0592 100644 --- a/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/SimpleClassLoaderLifeCycle.java +++ b/scm-webapp/src/main/java/sonia/scm/lifecycle/classloading/SimpleClassLoaderLifeCycle.java @@ -9,7 +9,7 @@ import java.util.ArrayDeque; import java.util.Deque; /** - * Creates and shutdown SCM-Manager ClassLoaders with ClassLoader leak detection. + * Creates and shutdown SCM-Manager ClassLoaders without ClassLoader leak detection. */ class SimpleClassLoaderLifeCycle extends ClassLoaderLifeCycle { From 94add329eb2f9cb1cbcf3cf3c9aa0bbf9a1d4bbd Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Thu, 6 Feb 2020 10:00:44 +0100 Subject: [PATCH 12/14] Remove wait time for leak preventor This is no longer necessary due to the new SimpleClassLoaderLifeCycle, that is configured in the jetty startup for integration tests in the poms of scm-webapp and scm-it. --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 839f9ce3b0..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') { From 6e9524f8093c80aaa5752cfeb365450e505e3df7 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Thu, 6 Feb 2020 11:52:24 +0100 Subject: [PATCH 13/14] Fix JavaDoc builds for java > 8 Sadly, therefor we have to remove the APIviz lib. It seems this is dead. --- pom.xml | 37 +++---------------- .../src/main/java/sonia/scm/i18n/I18n.java | 4 +- scm-core/pom.xml | 24 +++--------- 3 files changed, 13 insertions(+), 52 deletions(-) diff --git a/pom.xml b/pom.xml index 68735f4018..48e94c11de 100644 --- a/pom.xml +++ b/pom.xml @@ -434,7 +434,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.0.1 + 3.1.1 org.apache.maven.plugins @@ -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 - - - - - - - - @@ -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 36e0cb023e..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} - + @@ -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 - - From 2c4176752c970aa256b95a545e6b64304771eb2a Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Thu, 6 Feb 2020 11:51:46 +0000 Subject: [PATCH 14/14] Close branch feature/java_9_and_above