diff --git a/CHANGELOG.md b/CHANGELOG.md index f7c9397261..b3871922f1 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 - Detect renamed files in git and hg diffs ([#1157](https://github.com/scm-manager/scm-manager/pull/1157)) +- ClassLoader and Adapter parameters to typed store apis ([#1111](https://github.com/scm-manager/scm-manager/pull/1111)) - Native packaging for Debian, Red Hat, Windows, Unix, Docker and Kubernetes ([#1165](https://github.com/scm-manager/scm-manager/pull/1165)) ### Fixed diff --git a/scm-core/pom.xml b/scm-core/pom.xml index 7d78979d96..a03c1bb72a 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -34,7 +34,6 @@ 2.0.0-SNAPSHOT - sonia.scm scm-core 2.0.0-SNAPSHOT scm-core diff --git a/scm-core/src/main/java/sonia/scm/store/BlobStoreFactory.java b/scm-core/src/main/java/sonia/scm/store/BlobStoreFactory.java index 05e7fd5033..e2477f1752 100644 --- a/scm-core/src/main/java/sonia/scm/store/BlobStoreFactory.java +++ b/scm-core/src/main/java/sonia/scm/store/BlobStoreFactory.java @@ -21,10 +21,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - -package sonia.scm.store; -import sonia.scm.repository.Repository; +package sonia.scm.store; /** * The BlobStoreFactory can be used to create a new or get an existing {@link BlobStore}s. @@ -46,7 +44,7 @@ import sonia.scm.repository.Repository; * * @author Sebastian Sdorra * @since 1.23 - * + * * @apiviz.landmark * @apiviz.uses sonia.scm.store.BlobStore */ @@ -66,66 +64,7 @@ public interface BlobStoreFactory { * @param name The name for the {@link BlobStore}. * @return Floating API to either specify a repository or directly build a global {@link BlobStore}. */ - default FloatingStoreParameters.Builder withName(String name) { - return new FloatingStoreParameters(this).new Builder(name); - } -} - -final class FloatingStoreParameters implements StoreParameters { - - private String name; - private String repositoryId; - - private final BlobStoreFactory factory; - - FloatingStoreParameters(BlobStoreFactory factory) { - this.factory = factory; - } - - @Override - public String getName() { - return name; - } - - @Override - public String getRepositoryId() { - return repositoryId; - } - - public class Builder { - - Builder(String name) { - FloatingStoreParameters.this.name = name; - } - - /** - * Use this to create or get a {@link BlobStore} for a specific repository. This step is optional. If you want to - * have a global {@link BlobStore}, omit this. - * @param repository The optional repository for the {@link BlobStore}. - * @return Floating API to finish the call. - */ - public FloatingStoreParameters.Builder forRepository(Repository repository) { - FloatingStoreParameters.this.repositoryId = repository.getId(); - return this; - } - - /** - * Use this to create or get a {@link BlobStore} for a specific repository. This step is optional. If you want to - * have a global {@link BlobStore}, omit this. - * @param repositoryId The id of the optional repository for the {@link BlobStore}. - * @return Floating API to finish the call. - */ - public FloatingStoreParameters.Builder forRepository(String repositoryId) { - FloatingStoreParameters.this.repositoryId = repositoryId; - return this; - } - - /** - * Creates or gets the {@link BlobStore} with the given name and (if specified) the given repository. If no - * repository is given, the {@link BlobStore} will be global. - */ - public BlobStore build(){ - return factory.getStore(FloatingStoreParameters.this); - } + default StoreParametersBuilder withName(String name) { + return new StoreParametersBuilder<>(name, this::getStore); } } diff --git a/scm-core/src/main/java/sonia/scm/store/ConfigurationEntryStore.java b/scm-core/src/main/java/sonia/scm/store/ConfigurationEntryStore.java index 7c82ee7310..92cd37c407 100644 --- a/scm-core/src/main/java/sonia/scm/store/ConfigurationEntryStore.java +++ b/scm-core/src/main/java/sonia/scm/store/ConfigurationEntryStore.java @@ -21,20 +21,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.store; -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.common.base.Predicate; - -//~--- JDK imports ------------------------------------------------------------ - import java.util.Collection; +import java.util.function.Predicate; /** - * A ConfigurationEntryStore can be used to store multiple entries of structured - * configuration data. Note: the default implementation use JAXB to + * A ConfigurationEntryStore can be used to store multiple entries of structured + * configuration data. Note: the default implementation use JAXB to * marshall the items. * * @author Sebastian Sdorra @@ -52,5 +47,5 @@ public interface ConfigurationEntryStore extends DataStore { * * @return filtered collection of values */ - public Collection getMatchingValues(Predicate predicate); + Collection getMatchingValues(Predicate predicate); } diff --git a/scm-core/src/main/java/sonia/scm/store/ConfigurationEntryStoreFactory.java b/scm-core/src/main/java/sonia/scm/store/ConfigurationEntryStoreFactory.java index 96cca1da3d..09f1217815 100644 --- a/scm-core/src/main/java/sonia/scm/store/ConfigurationEntryStoreFactory.java +++ b/scm-core/src/main/java/sonia/scm/store/ConfigurationEntryStoreFactory.java @@ -21,10 +21,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - -package sonia.scm.store; -import sonia.scm.repository.Repository; +package sonia.scm.store; /** * The ConfigurationEntryStoreFactory can be used to create new or get existing {@link ConfigurationEntryStore}s. @@ -51,7 +49,7 @@ import sonia.scm.repository.Repository; * * @author Sebastian Sdorra * @since 1.31 - * + * * @apiviz.landmark * @apiviz.uses sonia.scm.store.ConfigurationEntryStore */ @@ -72,67 +70,7 @@ public interface ConfigurationEntryStoreFactory { * @return Floating API to set the name and either specify a repository or directly build a global * {@link ConfigurationEntryStore}. */ - default TypedFloatingConfigurationEntryStoreParameters.Builder withType(Class type) { - return new TypedFloatingConfigurationEntryStoreParameters(this).new Builder(type); - } -} - -final class TypedFloatingConfigurationEntryStoreParameters { - - private final TypedStoreParametersImpl parameters = new TypedStoreParametersImpl<>(); - private final ConfigurationEntryStoreFactory factory; - - TypedFloatingConfigurationEntryStoreParameters(ConfigurationEntryStoreFactory factory) { - this.factory = factory; - } - - public class Builder { - - Builder(Class type) { - parameters.setType(type); - } - - /** - * Use this to set the name for the {@link ConfigurationEntryStore}. - * @param name The name for the {@link ConfigurationEntryStore}. - * @return Floating API to either specify a repository or directly build a global {@link ConfigurationEntryStore}. - */ - public OptionalRepositoryBuilder withName(String name) { - parameters.setName(name); - return new OptionalRepositoryBuilder(); - } - } - - public class OptionalRepositoryBuilder { - - /** - * Use this to create or get a {@link ConfigurationEntryStore} for a specific repository. This step is optional. If - * you want to have a global {@link ConfigurationEntryStore}, omit this. - * @param repository The optional repository for the {@link ConfigurationEntryStore}. - * @return Floating API to finish the call. - */ - public OptionalRepositoryBuilder forRepository(Repository repository) { - parameters.setRepositoryId(repository.getId()); - return this; - } - - /** - * Use this to create or get a {@link ConfigurationEntryStore} for a specific repository. This step is optional. If - * you want to have a global {@link ConfigurationEntryStore}, omit this. - * @param repositoryId The id of the optional repository for the {@link ConfigurationEntryStore}. - * @return Floating API to finish the call. - */ - public OptionalRepositoryBuilder forRepository(String repositoryId) { - parameters.setRepositoryId(repositoryId); - return this; - } - - /** - * Creates or gets the {@link ConfigurationEntryStore} with the given name and (if specified) the given repository. - * If no repository is given, the {@link ConfigurationEntryStore} will be global. - */ - public ConfigurationEntryStore build(){ - return factory.getStore(parameters); - } + default TypedStoreParametersBuilder> withType(Class type) { + return new TypedStoreParametersBuilder<>(type, this::getStore); } } diff --git a/scm-core/src/main/java/sonia/scm/store/ConfigurationStoreFactory.java b/scm-core/src/main/java/sonia/scm/store/ConfigurationStoreFactory.java index 6e8d2b18ff..8202f8425b 100644 --- a/scm-core/src/main/java/sonia/scm/store/ConfigurationStoreFactory.java +++ b/scm-core/src/main/java/sonia/scm/store/ConfigurationStoreFactory.java @@ -21,12 +21,10 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.store; -import sonia.scm.repository.Repository; - /** * The ConfigurationStoreFactory can be used to create new or get existing {@link ConfigurationStore} objects. *
@@ -51,7 +49,7 @@ import sonia.scm.repository.Repository; * * * @author Sebastian Sdorra - * + * * @apiviz.landmark * @apiviz.uses sonia.scm.store.ConfigurationStore */ @@ -72,67 +70,7 @@ public interface ConfigurationStoreFactory { * @return Floating API to set the name and either specify a repository or directly build a global * {@link ConfigurationStore}. */ - default TypedFloatingConfigurationStoreParameters.Builder withType(Class type) { - return new TypedFloatingConfigurationStoreParameters(this).new Builder(type); - } -} - -final class TypedFloatingConfigurationStoreParameters { - - private final TypedStoreParametersImpl parameters = new TypedStoreParametersImpl<>(); - private final ConfigurationStoreFactory factory; - - TypedFloatingConfigurationStoreParameters(ConfigurationStoreFactory factory) { - this.factory = factory; - } - - public class Builder { - - Builder(Class type) { - parameters.setType(type); - } - - /** - * Use this to set the name for the {@link ConfigurationStore}. - * @param name The name for the {@link ConfigurationStore}. - * @return Floating API to either specify a repository or directly build a global {@link ConfigurationStore}. - */ - public OptionalRepositoryBuilder withName(String name) { - parameters.setName(name); - return new OptionalRepositoryBuilder(); - } - } - - public class OptionalRepositoryBuilder { - - /** - * Use this to create or get a {@link ConfigurationStore} for a specific repository. This step is optional. If you - * want to have a global {@link ConfigurationStore}, omit this. - * @param repository The optional repository for the {@link ConfigurationStore}. - * @return Floating API to finish the call. - */ - public OptionalRepositoryBuilder forRepository(Repository repository) { - parameters.setRepositoryId(repository.getId()); - return this; - } - - /** - * Use this to create or get a {@link ConfigurationStore} for a specific repository. This step is optional. If you - * want to have a global {@link ConfigurationStore}, omit this. - * @param repositoryId The id of the optional repository for the {@link ConfigurationStore}. - * @return Floating API to finish the call. - */ - public OptionalRepositoryBuilder forRepository(String repositoryId) { - parameters.setRepositoryId(repositoryId); - return this; - } - - /** - * Creates or gets the {@link ConfigurationStore} with the given name and (if specified) the given repository. If no - * repository is given, the {@link ConfigurationStore} will be global. - */ - public ConfigurationStore build(){ - return factory.getStore(parameters); - } + default TypedStoreParametersBuilder> withType(Class type) { + return new TypedStoreParametersBuilder<>(type, this::getStore); } } diff --git a/scm-core/src/main/java/sonia/scm/store/DataStoreFactory.java b/scm-core/src/main/java/sonia/scm/store/DataStoreFactory.java index c7ebbbb67f..45d9a93993 100644 --- a/scm-core/src/main/java/sonia/scm/store/DataStoreFactory.java +++ b/scm-core/src/main/java/sonia/scm/store/DataStoreFactory.java @@ -21,10 +21,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - -package sonia.scm.store; -import sonia.scm.repository.Repository; +package sonia.scm.store; /** * The DataStoreFactory can be used to create new or get existing {@link DataStore}s. @@ -48,7 +46,7 @@ import sonia.scm.repository.Repository; * * @author Sebastian Sdorra * @since 1.23 - * + * * @apiviz.landmark * @apiviz.uses sonia.scm.store.DataStore */ @@ -69,67 +67,7 @@ public interface DataStoreFactory { * @return Floating API to set the name and either specify a repository or directly build a global * {@link DataStore}. */ - default TypedFloatingDataStoreParameters.Builder withType(Class type) { - return new TypedFloatingDataStoreParameters(this).new Builder(type); - } -} - -final class TypedFloatingDataStoreParameters { - - private final TypedStoreParametersImpl parameters = new TypedStoreParametersImpl<>(); - private final DataStoreFactory factory; - - TypedFloatingDataStoreParameters(DataStoreFactory factory) { - this.factory = factory; - } - - public class Builder { - - Builder(Class type) { - parameters.setType(type); - } - - /** - * Use this to set the name for the {@link DataStore}. - * @param name The name for the {@link DataStore}. - * @return Floating API to either specify a repository or directly build a global {@link DataStore}. - */ - public OptionalRepositoryBuilder withName(String name) { - parameters.setName(name); - return new OptionalRepositoryBuilder(); - } - } - - public class OptionalRepositoryBuilder { - - /** - * Use this to create or get a {@link DataStore} for a specific repository. This step is optional. If you - * want to have a global {@link DataStore}, omit this. - * @param repository The optional repository for the {@link DataStore}. - * @return Floating API to finish the call. - */ - public OptionalRepositoryBuilder forRepository(Repository repository) { - parameters.setRepositoryId(repository.getId()); - return this; - } - - /** - * Use this to create or get a {@link DataStore} for a specific repository. This step is optional. If you - * want to have a global {@link DataStore}, omit this. - * @param repositoryId The id of the optional repository for the {@link DataStore}. - * @return Floating API to finish the call. - */ - public OptionalRepositoryBuilder forRepository(String repositoryId) { - parameters.setRepositoryId(repositoryId); - return this; - } - - /** - * Creates or gets the {@link DataStore} with the given name and (if specified) the given repository. If no - * repository is given, the {@link DataStore} will be global. - */ - public DataStore build(){ - return factory.getStore(parameters); - } + default TypedStoreParametersBuilder> withType(Class type) { + return new TypedStoreParametersBuilder<>(type, this::getStore); } } diff --git a/scm-core/src/main/java/sonia/scm/store/StoreParameters.java b/scm-core/src/main/java/sonia/scm/store/StoreParameters.java index 379013388a..2326a68803 100644 --- a/scm-core/src/main/java/sonia/scm/store/StoreParameters.java +++ b/scm-core/src/main/java/sonia/scm/store/StoreParameters.java @@ -21,10 +21,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - -package sonia.scm.store; -import sonia.scm.repository.Repository; +package sonia.scm.store; /** * The fields of the {@link StoreParameters} are used from the {@link BlobStoreFactory} to create a store. diff --git a/scm-core/src/main/java/sonia/scm/store/StoreParametersBuilder.java b/scm-core/src/main/java/sonia/scm/store/StoreParametersBuilder.java new file mode 100644 index 0000000000..38cd208a7d --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/store/StoreParametersBuilder.java @@ -0,0 +1,90 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.store; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import sonia.scm.repository.Repository; + +import java.util.function.Function; + +/** + * Builder for {@link StoreParameters}. + * + * @param type of store + */ +public final class StoreParametersBuilder { + + private final StoreParametersImpl parameters; + private final Function factory; + + StoreParametersBuilder(String name, Function factory) { + this.parameters = new StoreParametersImpl(name); + this.factory = factory; + } + + @Getter + @Setter(AccessLevel.PRIVATE) + @RequiredArgsConstructor + private static class StoreParametersImpl implements StoreParameters { + private final String name; + private String repositoryId; + } + + + /** + * Use this to create or get a store for a specific repository. This step is optional. If you want to + * have a global store, omit this. + * + * @param repository The optional repository for the store. + * @return Floating API to finish the call. + */ + public StoreParametersBuilder forRepository(Repository repository) { + parameters.repositoryId = repository.getId(); + return this; + } + + /** + * Use this to create or get a store for a specific repository. This step is optional. If you want to + * have a global store, omit this. + * + * @param repositoryId The id of the optional repository for the store. + * @return Floating API to finish the call. + */ + public StoreParametersBuilder forRepository(String repositoryId) { + parameters.repositoryId = repositoryId; + return this; + } + + /** + * Creates or gets the store with the given name and (if specified) the given repository. If no + * repository is given, the store will be global. + */ + public S build() { + return factory.apply(parameters); + } +} diff --git a/scm-core/src/main/java/sonia/scm/store/TypedStoreParameters.java b/scm-core/src/main/java/sonia/scm/store/TypedStoreParameters.java index c07df2b4a0..4f80078743 100644 --- a/scm-core/src/main/java/sonia/scm/store/TypedStoreParameters.java +++ b/scm-core/src/main/java/sonia/scm/store/TypedStoreParameters.java @@ -21,10 +21,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.store; -import sonia.scm.repository.Repository; +import javax.xml.bind.annotation.adapters.XmlAdapter; +import java.util.Optional; +import java.util.Set; /** * The fields of the {@link TypedStoreParameters} are used from the {@link ConfigurationStoreFactory}, @@ -33,11 +35,13 @@ import sonia.scm.repository.Repository; * @author Mohamed Karray * @since 2.0.0 */ -public interface TypedStoreParameters { +public interface TypedStoreParameters extends StoreParameters { Class getType(); - String getName(); + Optional getClassLoader(); + + @SuppressWarnings("java:S1452") // we could not provide generic type, because we don't know it here + Set> getAdapters(); - String getRepositoryId(); } diff --git a/scm-core/src/main/java/sonia/scm/store/TypedStoreParametersBuilder.java b/scm-core/src/main/java/sonia/scm/store/TypedStoreParametersBuilder.java new file mode 100644 index 0000000000..efda2187a6 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/store/TypedStoreParametersBuilder.java @@ -0,0 +1,143 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.store; + +import com.google.common.collect.ImmutableSet; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import sonia.scm.plugin.PluginLoader; +import sonia.scm.repository.Repository; + +import javax.xml.bind.annotation.adapters.XmlAdapter; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +/** + * Builder for {@link TypedStoreParameters}. + * + * @param type of data + * @param type of store + */ +public final class TypedStoreParametersBuilder { + + private final TypedStoreParametersImpl parameters; + private final Function, S> factory; + + TypedStoreParametersBuilder(Class type, Function, S> factory) { + this.factory = factory; + this.parameters = new TypedStoreParametersImpl<>(type); + } + + /** + * Use this to set the name for the store. + * @param name The name for the store. + * @return Floating API to either specify a repository or directly build a global store. + */ + public OptionalRepositoryBuilder withName(String name) { + parameters.setName(name); + return new OptionalRepositoryBuilder(); + } + + @Getter + @Setter(AccessLevel.PRIVATE) + @RequiredArgsConstructor + private static class TypedStoreParametersImpl implements TypedStoreParameters { + private final Class type; + private String name; + private String repositoryId; + private ClassLoader classLoader; + private Set> adapters; + + public Optional getClassLoader() { + return Optional.ofNullable(classLoader); + } + } + + public class OptionalRepositoryBuilder { + + private final Set> adapters = new HashSet<>(); + + /** + * Use this to create or get a store for a specific repository. This step is optional. If you + * want to have a global store, omit this. + * @param repository The optional repository for the store. + * @return Floating API to finish the call. + */ + public OptionalRepositoryBuilder forRepository(Repository repository) { + parameters.setRepositoryId(repository.getId()); + return this; + } + + /** + * Use this to create or get a store for a specific repository. This step is optional. If you + * want to have a global store, omit this. + * @param repositoryId The id of the optional repository for the store. + * @return Floating API to finish the call. + */ + public OptionalRepositoryBuilder forRepository(String repositoryId) { + parameters.setRepositoryId(repositoryId); + return this; + } + + /** + * Sets the {@link ClassLoader} which is used as context class loader during marshaling and unmarshalling. + * This is especially useful for storing class objects which come from an unknown source, in this case the + * UberClassLoader ({@link PluginLoader#getUberClassLoader()} could be used for the store. + * + * @param classLoader classLoader for the context + * + * @return {@code this} + */ + public OptionalRepositoryBuilder withClassLoader(ClassLoader classLoader) { + parameters.setClassLoader(classLoader); + return this; + } + + /** + * Sets an instance of an {@link XmlAdapter}. + * + * @param adapter adapter + * @return {@code this} + */ + public OptionalRepositoryBuilder withAdapter(XmlAdapter adapter) { + adapters.add(adapter); + return this; + } + + /** + * Creates or gets the store with the given name and (if specified) the given repository. If no + * repository is given, the store will be global. + */ + public S build(){ + parameters.setAdapters(ImmutableSet.copyOf(adapters)); + return factory.apply(parameters); + } + } + +} diff --git a/scm-core/src/main/java/sonia/scm/store/TypedStoreParametersImpl.java b/scm-core/src/main/java/sonia/scm/store/TypedStoreParametersImpl.java deleted file mode 100644 index 6d99185302..0000000000 --- a/scm-core/src/main/java/sonia/scm/store/TypedStoreParametersImpl.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2020-present Cloudogu GmbH and Contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package sonia.scm.store; - -class TypedStoreParametersImpl implements TypedStoreParameters { - private Class type; - private String name; - private String repositoryId; - - @Override - public Class getType() { - return type; - } - - void setType(Class type) { - this.type = type; - } - - @Override - public String getName() { - return name; - } - - void setName(String name) { - this.name = name; - } - - @Override - public String getRepositoryId() { - return repositoryId; - } - - void setRepositoryId(String repositoryId) { - this.repositoryId = repositoryId; - } -} diff --git a/scm-dao-xml/pom.xml b/scm-dao-xml/pom.xml index 42f49dd8f3..bfb331ede5 100644 --- a/scm-dao-xml/pom.xml +++ b/scm-dao-xml/pom.xml @@ -34,7 +34,6 @@ 2.0.0-SNAPSHOT - sonia.scm scm-dao-xml 2.0.0-SNAPSHOT scm-dao-xml diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java index b25c1c9e9a..b5d20aad53 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStore.java @@ -21,12 +21,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.store; -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.common.base.Predicate; import com.google.common.collect.Collections2; import com.google.common.collect.Maps; import org.slf4j.Logger; @@ -35,11 +32,8 @@ import sonia.scm.security.KeyGenerator; import sonia.scm.xml.IndentXMLStreamWriter; import sonia.scm.xml.XmlStreams; -import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; -import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; -import javax.xml.bind.Unmarshaller; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamReader; import java.io.File; @@ -47,98 +41,52 @@ import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Map.Entry; +import java.util.function.Predicate; -//~--- JDK imports ------------------------------------------------------------ +import static javax.xml.stream.XMLStreamConstants.END_ELEMENT; -/** - * - * @author Sebastian Sdorra - * - * @param - */ -public class JAXBConfigurationEntryStore implements ConfigurationEntryStore -{ +public class JAXBConfigurationEntryStore implements ConfigurationEntryStore { - /** Field description */ private static final String TAG_CONFIGURATION = "configuration"; - - /** Field description */ private static final String TAG_ENTRY = "entry"; - - /** Field description */ private static final String TAG_KEY = "key"; - - /** Field description */ private static final String TAG_VALUE = "value"; /** * the logger for JAXBConfigurationEntryStore */ - private static final Logger logger = - LoggerFactory.getLogger(JAXBConfigurationEntryStore.class); + private static final Logger LOG = LoggerFactory.getLogger(JAXBConfigurationEntryStore.class); - //~--- constructors --------------------------------------------------------- - /** - * Constructs ... - * - * - * - * @param keyGenerator - * @param file - * @param type - */ - JAXBConfigurationEntryStore(File file, KeyGenerator keyGenerator, - Class type) - { + private final File file; + private final KeyGenerator keyGenerator; + private final Class type; + private final TypedStoreContext context; + private final Map entries = Maps.newHashMap(); + + JAXBConfigurationEntryStore(File file, KeyGenerator keyGenerator, Class type, TypedStoreContext context) { this.file = file; this.keyGenerator = keyGenerator; this.type = type; - - try - { - this.context = JAXBContext.newInstance(type); - - if (file.exists()) - { - load(); - } - } - catch (JAXBException ex) - { - throw new StoreException("could not create jaxb context", ex); + this.context = context; + // initial load + if (file.exists()) { + load(); } } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - */ @Override - public void clear() - { - logger.debug("clear configuration store"); + public void clear() { + LOG.debug("clear configuration store"); - synchronized (file) - { + synchronized (file) { entries.clear(); store(); } } - /** - * Method description - * - * - * @param item - * - * @return - */ @Override - public String put(V item) - { + public String put(V item) { String id = keyGenerator.createKey(); put(id, item); @@ -146,225 +94,138 @@ public class JAXBConfigurationEntryStore implements ConfigurationEntryStore getAll() - { - logger.trace("get all items from configuration store"); + public Map getAll() { + LOG.trace("get all items from configuration store"); return Collections.unmodifiableMap(entries); } - /** - * Method description - * - * - * @param predicate - * - * @return - */ @Override - public Collection getMatchingValues(Predicate predicate) - { - return Collections2.filter(entries.values(), predicate); + public Collection getMatchingValues(Predicate predicate) { + return Collections2.filter(entries.values(), predicate::test); } - //~--- methods -------------------------------------------------------------- + private void load() { + LOG.debug("load configuration from {}", file); - /** - * Method description - * - * - * @return - */ - private void load() - { - logger.debug("load configuration from {}", file); + context.withUnmarshaller(u -> { + XMLStreamReader reader = null; + try { + reader = XmlStreams.createReader(file); - XMLStreamReader reader = null; - - try - { - Unmarshaller u = context.createUnmarshaller(); - - reader = XmlStreams.createReader(file); - - // configuration - reader.nextTag(); - - // entry start - reader.nextTag(); - - while (reader.isStartElement() && reader.getLocalName().equals(TAG_ENTRY)) - { - - // read key + // configuration reader.nextTag(); - String key = reader.getElementText(); - - // read value + // entry start reader.nextTag(); - JAXBElement element = u.unmarshal(reader, type); + while (reader.isStartElement() && reader.getLocalName().equals(TAG_ENTRY)) { - if (!element.isNil()) - { - V v = element.getValue(); - - logger.trace("add element {} to configuration entry store", v); - - entries.put(key, v); - } - else - { - logger.warn("could not unmarshall object of entry store"); - } - - // closed or new entry tag - if (reader.nextTag() == XMLStreamReader.END_ELEMENT) - { - - // fixed format, start new entry + // read key reader.nextTag(); - } - } - } - catch (Exception ex) - { - throw new StoreException("could not load configuration", ex); - } - finally - { - XmlStreams.close(reader); - } - } - /** - * Method description - * - */ - private void store() - { - logger.debug("store configuration to {}", file); + String key = reader.getElementText(); - CopyOnWrite.withTemporaryFile( - temp -> { - try (IndentXMLStreamWriter writer = XmlStreams.createWriter(temp)) { - writer.writeStartDocument(); + // read value + reader.nextTag(); - // configuration start - writer.writeStartElement(TAG_CONFIGURATION); + JAXBElement element = u.unmarshal(reader, type); - Marshaller m = context.createMarshaller(); + if (!element.isNil()) { + V v = element.getValue(); - m.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE); + LOG.trace("add element {} to configuration entry store", v); - for (Entry e : entries.entrySet()) { - - // entry start - writer.writeStartElement(TAG_ENTRY); - - // key start - writer.writeStartElement(TAG_KEY); - writer.writeCharacters(e.getKey()); - - // key end - writer.writeEndElement(); - - // value - JAXBElement je = new JAXBElement<>(QName.valueOf(TAG_VALUE), type, - e.getValue()); - - m.marshal(je, writer); - - // entry end - writer.writeEndElement(); + entries.put(key, v); + } else { + LOG.warn("could not unmarshall object of entry store"); } - // configuration end - writer.writeEndElement(); - writer.writeEndDocument(); + // closed or new entry tag + if (reader.nextTag() == END_ELEMENT) { + + // fixed format, start new entry + reader.nextTag(); + } } - }, - file.toPath() - ); + } finally { + XmlStreams.close(reader); + } + }); } - //~--- fields --------------------------------------------------------------- + private void store() { + LOG.debug("store configuration to {}", file); - /** Field description */ - private final File file; + context.withMarshaller(m -> { + m.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE); - /** Field description */ - private JAXBContext context; + CopyOnWrite.withTemporaryFile( + temp -> { + try (IndentXMLStreamWriter writer = XmlStreams.createWriter(temp)) { + writer.writeStartDocument(); - /** Field description */ - private Map entries = Maps.newHashMap(); + // configuration start + writer.writeStartElement(TAG_CONFIGURATION); - /** Field description */ - private KeyGenerator keyGenerator; - /** Field description */ - private Class type; + for (Entry e : entries.entrySet()) { + + // entry start + writer.writeStartElement(TAG_ENTRY); + + // key start + writer.writeStartElement(TAG_KEY); + writer.writeCharacters(e.getKey()); + + // key end + writer.writeEndElement(); + + // value + JAXBElement je = new JAXBElement<>(QName.valueOf(TAG_VALUE), type, + e.getValue()); + + m.marshal(je, writer); + + // entry end + writer.writeEndElement(); + } + + // configuration end + writer.writeEndElement(); + writer.writeEndDocument(); + } + }, + file.toPath() + ); + }); + } } diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStoreFactory.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStoreFactory.java index 83294038d6..7feae4e0df 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStoreFactory.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationEntryStoreFactory.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.store; //~--- non-JDK imports -------------------------------------------------------- @@ -55,6 +55,8 @@ public class JAXBConfigurationEntryStoreFactory extends FileBasedStoreFactory return new JAXBConfigurationEntryStore<>( getStoreLocation(storeParameters.getName().concat(StoreConstants.FILE_EXTENSION), storeParameters.getType(), storeParameters.getRepositoryId()), keyGenerator, - storeParameters.getType()); + storeParameters.getType(), + TypedStoreContext.of(storeParameters) + ); } } diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationStore.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationStore.java index 5a641dcd29..6b2cf9993a 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationStore.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationStore.java @@ -21,26 +21,19 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.store; -//~--- non-JDK imports -------------------------------------------------------- import org.slf4j.Logger; import org.slf4j.LoggerFactory; -//~--- JDK imports ------------------------------------------------------------ import java.io.File; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Marshaller; - /** * JAXB implementation of {@link ConfigurationStore}. * - * @author Sebastian Sdorra - * * @param + * @author Sebastian Sdorra */ public class JAXBConfigurationStore extends AbstractStore { @@ -49,28 +42,19 @@ public class JAXBConfigurationStore extends AbstractStore { */ private static final Logger LOG = LoggerFactory.getLogger(JAXBConfigurationStore.class); - private Class type; - - private File configFile; - - private JAXBContext context; - - public JAXBConfigurationStore(Class type, File configFile) { - this.type = type; + private final TypedStoreContext context; + private final Class type; + private final File configFile; - try { - context = JAXBContext.newInstance(type); - this.configFile = configFile; - } - catch (JAXBException ex) { - throw new StoreException("failed to create jaxb context", ex); - } + public JAXBConfigurationStore(TypedStoreContext context, Class type, File configFile) { + this.context = context; + this.type = type; + this.configFile = configFile; } /** * Returns type of stored object. * - * * @return type */ public Class getType() { @@ -82,38 +66,18 @@ public class JAXBConfigurationStore extends AbstractStore { protected T readObject() { LOG.debug("load {} from store {}", type, configFile); - T result = null; - if (configFile.exists()) { - try { - result = (T) context.createUnmarshaller().unmarshal(configFile); - } - catch (JAXBException ex) { - throw new StoreException("failed to unmarshall object", ex); - } + return context.unmarshall(configFile); } - - return result; + return null; } @Override protected void writeObject(T object) { - if (LOG.isDebugEnabled()) { - LOG.debug("store {} to {}", object.getClass().getName(), - configFile.getPath()); - } - - try { - Marshaller marshaller = context.createMarshaller(); - - marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); - CopyOnWrite.withTemporaryFile( - temp -> marshaller.marshal(object, temp.toFile()), - configFile.toPath() - ); - } - catch (JAXBException ex) { - throw new StoreException("failed to marshall object", ex); - } + LOG.debug("store {} to {}", object.getClass().getName(), configFile.getPath()); + CopyOnWrite.withTemporaryFile( + temp -> context.marshal(object, temp.toFile()), + configFile.toPath() + ); } } diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationStoreFactory.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationStoreFactory.java index 65a81c8b7b..29cdf471a3 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationStoreFactory.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBConfigurationStoreFactory.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.store; import com.google.inject.Inject; @@ -49,10 +49,13 @@ public class JAXBConfigurationStoreFactory extends FileBasedStoreFactory impleme @Override public JAXBConfigurationStore getStore(TypedStoreParameters storeParameters) { + TypedStoreContext context = TypedStoreContext.of(storeParameters); return new JAXBConfigurationStore<>( + context, storeParameters.getType(), getStoreLocation(storeParameters.getName().concat(StoreConstants.FILE_EXTENSION), storeParameters.getType(), - storeParameters.getRepositoryId())); + storeParameters.getRepositoryId()) + ); } } diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBDataStore.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBDataStore.java index 036fe27f9c..e963c58983 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBDataStore.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBDataStore.java @@ -24,23 +24,16 @@ package sonia.scm.store; -//~--- non-JDK imports -------------------------------------------------------- import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.security.KeyGenerator; -//~--- JDK imports ------------------------------------------------------------ -import java.io.File; - -import java.util.Map; - -import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; +import java.io.File; +import java.util.Map; /** * Jaxb implementation of {@link DataStore}. @@ -54,24 +47,16 @@ public class JAXBDataStore extends FileBasedStore implements DataStore /** * the logger for JAXBDataStore */ - private static final Logger LOG - = LoggerFactory.getLogger(JAXBDataStore.class); - - private final JAXBContext context; + private static final Logger LOG = LoggerFactory.getLogger(JAXBDataStore.class); private final KeyGenerator keyGenerator; + private final TypedStoreContext context; - JAXBDataStore(KeyGenerator keyGenerator, Class type, File directory) { + JAXBDataStore(KeyGenerator keyGenerator, TypedStoreContext context, File directory) { super(directory, StoreConstants.FILE_EXTENSION); this.keyGenerator = keyGenerator; - - try { - context = JAXBContext.newInstance(type); - this.directory = directory; - } - catch (JAXBException ex) { - throw new StoreException("failed to create jaxb context", ex); - } + this.directory = directory; + this.context = context; } @Override @@ -118,22 +103,11 @@ public class JAXBDataStore extends FileBasedStore implements DataStore } @Override - @SuppressWarnings("unchecked") protected T read(File file) { - T item = null; - if (file.exists()) { LOG.trace("try to read {}", file); - - try { - item = (T) context.createUnmarshaller().unmarshal(file); - } - catch (JAXBException ex) { - throw new StoreException( - "could not read object ".concat(file.getPath()), ex); - } + return context.unmarshall(file); } - - return item; + return null; } } diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBDataStoreFactory.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBDataStoreFactory.java index d525b5c7a6..80e2d260d2 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBDataStoreFactory.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBDataStoreFactory.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.store; //~--- non-JDK imports -------------------------------------------------------- @@ -56,6 +56,6 @@ public class JAXBDataStoreFactory extends FileBasedStoreFactory public DataStore getStore(TypedStoreParameters storeParameters) { File storeLocation = getStoreLocation(storeParameters); IOUtil.mkdirs(storeLocation); - return new JAXBDataStore<>(keyGenerator, storeParameters.getType(), storeLocation); + return new JAXBDataStore<>(keyGenerator, TypedStoreContext.of(storeParameters), storeLocation); } } diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/TypedStoreContext.java b/scm-dao-xml/src/main/java/sonia/scm/store/TypedStoreContext.java new file mode 100644 index 0000000000..58e3e1efdc --- /dev/null +++ b/scm-dao-xml/src/main/java/sonia/scm/store/TypedStoreContext.java @@ -0,0 +1,127 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.store; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.adapters.XmlAdapter; +import java.io.File; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; + +final class TypedStoreContext { + + private final JAXBContext jaxbContext; + private final TypedStoreParameters parameters; + + private TypedStoreContext(JAXBContext jaxbContext, TypedStoreParameters parameters) { + this.jaxbContext = jaxbContext; + this.parameters = parameters; + } + + static TypedStoreContext of(TypedStoreParameters parameters) { + try { + JAXBContext jaxbContext = JAXBContext.newInstance(parameters.getType()); + return new TypedStoreContext<>(jaxbContext, parameters); + } catch (JAXBException e) { + throw new StoreException("failed to create context for store"); + } + } + + T unmarshall(File file) { + AtomicReference ref = new AtomicReference<>(); + withUnmarshaller(unmarshaller -> { + T value = parameters.getType().cast(unmarshaller.unmarshal(file)); + ref.set(value); + }); + return ref.get(); + } + + void marshal(Object object, File file) { + withMarshaller(marshaller -> marshaller.marshal(object, file)); + } + + void withMarshaller(ThrowingConsumer consumer) { + Marshaller marshaller = createMarshaller(); + withClassLoader(consumer, marshaller); + } + + void withUnmarshaller(ThrowingConsumer consumer) { + Unmarshaller unmarshaller = createUnmarshaller(); + withClassLoader(consumer, unmarshaller); + } + + private void withClassLoader(ThrowingConsumer consumer, C consume) { + ClassLoader contextClassLoader = null; + Optional classLoader = parameters.getClassLoader(); + if (classLoader.isPresent()) { + contextClassLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(classLoader.get()); + } + try { + consumer.consume(consume); + } catch (Exception e) { + throw new StoreException("failure during marshalling/unmarshalling", e); + } finally { + if (contextClassLoader != null) { + Thread.currentThread().setContextClassLoader(contextClassLoader); + } + } + } + + Marshaller createMarshaller() { + try { + Marshaller marshaller = jaxbContext.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + for (XmlAdapter adapter : parameters.getAdapters()) { + marshaller.setAdapter(adapter); + } + return marshaller; + } catch (JAXBException e) { + throw new StoreException("could not create marshaller", e); + } + } + + private Unmarshaller createUnmarshaller() { + try { + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + for (XmlAdapter adapter : parameters.getAdapters()) { + unmarshaller.setAdapter(adapter); + } + return unmarshaller; + } catch (JAXBException e) { + throw new StoreException("could not create unmarshaller", e); + } + } + + @FunctionalInterface + interface ThrowingConsumer { + @SuppressWarnings("java:S112") // we need to throw Exception here + void consume(T item) throws Exception; + } + +} diff --git a/scm-dao-xml/src/test/java/sonia/scm/store/JAXBDataStoreTest.java b/scm-dao-xml/src/test/java/sonia/scm/store/JAXBDataStoreTest.java index 54057cfad6..13b9ad29b8 100644 --- a/scm-dao-xml/src/test/java/sonia/scm/store/JAXBDataStoreTest.java +++ b/scm-dao-xml/src/test/java/sonia/scm/store/JAXBDataStoreTest.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.store; //~--- non-JDK imports -------------------------------------------------------- @@ -52,7 +52,7 @@ public class JAXBDataStoreTest extends DataStoreTestBase { } @Override - protected DataStore getDataStore(Class type, Repository repository) { + protected DataStore getDataStore(Class type, Repository repository) { return createDataStoreFactory() .withType(type) .withName("test") @@ -61,7 +61,7 @@ public class JAXBDataStoreTest extends DataStoreTestBase { } @Override - protected DataStore getDataStore(Class type) { + protected DataStore getDataStore(Class type) { return createDataStoreFactory() .withType(type) .withName("test") diff --git a/scm-dao-xml/src/test/java/sonia/scm/store/TypedStoreContextTest.java b/scm-dao-xml/src/test/java/sonia/scm/store/TypedStoreContextTest.java new file mode 100644 index 0000000000..66a390d2ae --- /dev/null +++ b/scm-dao-xml/src/test/java/sonia/scm/store/TypedStoreContextTest.java @@ -0,0 +1,174 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.store; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlAdapter; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class TypedStoreContextTest { + + @Test + void shouldMarshallAndUnmarshall(@TempDir Path tempDir) { + TypedStoreContext context = context(Sample.class); + + File file = tempDir.resolve("test.xml").toFile(); + context.marshal(new Sample("awesome"), file); + Sample sample = context.unmarshall(file); + + assertThat(sample.value).isEqualTo("awesome"); + } + + @Test + void shouldWorkWithMarshallerAndUnmarshaller(@TempDir Path tempDir) { + TypedStoreContext context = context(Sample.class); + + File file = tempDir.resolve("test.xml").toFile(); + + context.withMarshaller(marshaller -> { + marshaller.marshal(new Sample("wow"), file); + }); + + AtomicReference ref = new AtomicReference<>(); + + context.withUnmarshaller(unmarshaller -> { + Sample sample = (Sample) unmarshaller.unmarshal(file); + ref.set(sample); + }); + + assertThat(ref.get().value).isEqualTo("wow"); + } + + @Test + void shouldSetContextClassLoader() { + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + + ClassLoader classLoader = new URLClassLoader(new URL[0], contextClassLoader); + + TypedStoreParameters params = params(Sample.class); + when(params.getClassLoader()).thenReturn(Optional.of(classLoader)); + + TypedStoreContext context = TypedStoreContext.of(params); + + AtomicReference ref = new AtomicReference<>(); + context.withMarshaller(marshaller -> { + ref.set(Thread.currentThread().getContextClassLoader()); + }); + + assertThat(ref.get()).isSameAs(classLoader); + assertThat(Thread.currentThread().getContextClassLoader()).isSameAs(contextClassLoader); + } + + @Test + void shouldConfigureAdapter(@TempDir Path tempDir) { + TypedStoreParameters params = params(SampleWithAdapter.class); + when(params.getAdapters()).thenReturn(Collections.singleton(new AppendingAdapter("!"))); + + TypedStoreContext context = TypedStoreContext.of(params); + + File file = tempDir.resolve("test.xml").toFile(); + context.marshal(new SampleWithAdapter("awesome"), file); + SampleWithAdapter sample = context.unmarshall(file); + + // one ! should be added for marshal and one for unmarshal + assertThat(sample.value).isEqualTo("awesome!!"); + } + + private TypedStoreContext context(Class type) { + return TypedStoreContext.of(params(type)); + } + + private TypedStoreParameters params(Class type) { + TypedStoreParameters params = mock(TypedStoreParameters.class); + when(params.getType()).thenReturn(type); + return params; + } + + @XmlRootElement + @XmlAccessorType(XmlAccessType.FIELD) + public static class Sample { + private String value; + + public Sample() { + } + + public Sample(String value) { + this.value = value; + } + } + + @XmlRootElement + @XmlAccessorType(XmlAccessType.FIELD) + public static class SampleWithAdapter { + @XmlJavaTypeAdapter(AppendingAdapter.class) + private String value; + + public SampleWithAdapter() { + } + + public SampleWithAdapter(String value) { + this.value = value; + } + } + + public static class AppendingAdapter extends XmlAdapter { + + private final String suffix; + + public AppendingAdapter(String suffix) { + this.suffix = suffix; + } + + @Override + public String unmarshal(String v) { + return v + suffix; + } + + @Override + public String marshal(String v) { + return v + suffix; + } + } + +} diff --git a/scm-test/src/main/java/sonia/scm/store/ConfigurationEntryStoreTestBase.java b/scm-test/src/main/java/sonia/scm/store/ConfigurationEntryStoreTestBase.java index b90af5c970..ff44f6cbd2 100644 --- a/scm-test/src/main/java/sonia/scm/store/ConfigurationEntryStoreTestBase.java +++ b/scm-test/src/main/java/sonia/scm/store/ConfigurationEntryStoreTestBase.java @@ -21,13 +21,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.store; import sonia.scm.repository.Repository; /** - * * @author Sebastian Sdorra */ public abstract class ConfigurationEntryStoreTestBase extends KeyValueStoreTestBase { @@ -35,26 +34,25 @@ public abstract class ConfigurationEntryStoreTestBase extends KeyValueStoreTestB /** * Method description * - * * @return */ protected abstract ConfigurationEntryStoreFactory createConfigurationStoreFactory(); //~--- get methods ---------------------------------------------------------- @Override - protected ConfigurationEntryStore getDataStore(Class type) { + protected ConfigurationEntryStore getDataStore(Class type) { return this.createConfigurationStoreFactory() .withType(type) .withName(storeName) .build(); } - @Override - protected ConfigurationEntryStore getDataStore(Class type, Repository repository) { - return this.createConfigurationStoreFactory() - .withType(type) - .withName(repoStoreName) - .forRepository(repository) - .build(); + @Override + protected ConfigurationEntryStore getDataStore(Class type, Repository repository) { + return this.createConfigurationStoreFactory() + .withType(type) + .withName(repoStoreName) + .forRepository(repository) + .build(); } } diff --git a/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationEntryStore.java b/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationEntryStore.java index d82c1ec3a4..f150b4bdf8 100644 --- a/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationEntryStore.java +++ b/scm-test/src/main/java/sonia/scm/store/InMemoryConfigurationEntryStore.java @@ -21,16 +21,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.store; -import com.google.common.base.Predicate; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.function.Predicate; import java.util.stream.Collectors; public class InMemoryConfigurationEntryStore implements ConfigurationEntryStore {