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 {