mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-06-19 19:51:10 +02:00
added optional store parameter for ClassLoader and adapter
This commit is contained in:
@@ -24,6 +24,10 @@
|
||||
|
||||
package sonia.scm.store;
|
||||
|
||||
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},
|
||||
* {@link ConfigurationEntryStoreFactory} and {@link DataStoreFactory} to create a type safe store.
|
||||
@@ -35,4 +39,9 @@ public interface TypedStoreParameters<T> extends StoreParameters {
|
||||
|
||||
Class<T> getType();
|
||||
|
||||
Optional<ClassLoader> getClassLoader();
|
||||
|
||||
@SuppressWarnings("java:S1452") // we could not provide generic type, because we don't know it here
|
||||
Set<XmlAdapter<?,?>> getAdapters();
|
||||
|
||||
}
|
||||
|
||||
@@ -24,11 +24,18 @@
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -64,10 +71,18 @@ public final class TypedStoreParametersBuilder<T,S> {
|
||||
private final Class<T> type;
|
||||
private String name;
|
||||
private String repositoryId;
|
||||
private ClassLoader classLoader;
|
||||
private Set<XmlAdapter<?, ?>> adapters;
|
||||
|
||||
public Optional<ClassLoader> getClassLoader() {
|
||||
return Optional.ofNullable(classLoader);
|
||||
}
|
||||
}
|
||||
|
||||
public class OptionalRepositoryBuilder {
|
||||
|
||||
private final Set<XmlAdapter<?,?>> 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.
|
||||
@@ -90,11 +105,37 @@ public final class TypedStoreParametersBuilder<T,S> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,10 @@ 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<T> {
|
||||
|
||||
@@ -50,38 +53,53 @@ final class TypedStoreContext<T> {
|
||||
}
|
||||
|
||||
T unmarshall(File file) {
|
||||
Unmarshaller unmarshaller = createUnmarshaller();
|
||||
try {
|
||||
return parameters.getType().cast(unmarshaller.unmarshal(file));
|
||||
} catch (JAXBException e) {
|
||||
throw new StoreException("failed to unmarshall " + file);
|
||||
}
|
||||
AtomicReference<T> 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) {
|
||||
Marshaller marshaller = createMarshaller();
|
||||
try {
|
||||
marshaller.marshal(object, file);
|
||||
} catch (JAXBException e) {
|
||||
throw new StoreException("failed to marshall " + object + " to " + file);
|
||||
}
|
||||
withMarshaller(marshaller -> marshaller.marshal(object, file));
|
||||
}
|
||||
|
||||
void withMarshaller(ThrowingConsumer<Marshaller> consumer) {
|
||||
Marshaller marshaller = createMarshaller();
|
||||
ClassLoader contextClassLoader = null;
|
||||
Optional<ClassLoader> classLoader = parameters.getClassLoader();
|
||||
if (classLoader.isPresent()) {
|
||||
contextClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
Thread.currentThread().setContextClassLoader(classLoader.get());
|
||||
}
|
||||
try {
|
||||
consumer.consume(marshaller);
|
||||
} catch (Exception e) {
|
||||
throw new StoreException("failure during work with marshaller");
|
||||
throw new StoreException("failure during work with marshaller", e);
|
||||
} finally {
|
||||
if (contextClassLoader != null) {
|
||||
Thread.currentThread().setContextClassLoader(contextClassLoader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void withUnmarshaller(ThrowingConsumer<Unmarshaller> consumer) {
|
||||
Unmarshaller unmarshaller = createUnmarshaller();
|
||||
ClassLoader contextClassLoader = null;
|
||||
Optional<ClassLoader> classLoader = parameters.getClassLoader();
|
||||
if (classLoader.isPresent()) {
|
||||
contextClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
Thread.currentThread().setContextClassLoader(classLoader.get());
|
||||
}
|
||||
try {
|
||||
consumer.consume(unmarshaller);
|
||||
} catch (Exception e) {
|
||||
throw new StoreException("failure during work with unmarshaller", e);
|
||||
} finally {
|
||||
if (contextClassLoader != null) {
|
||||
Thread.currentThread().setContextClassLoader(contextClassLoader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +107,9 @@ final class TypedStoreContext<T> {
|
||||
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);
|
||||
@@ -98,6 +119,9 @@ final class TypedStoreContext<T> {
|
||||
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);
|
||||
@@ -106,6 +130,7 @@ final class TypedStoreContext<T> {
|
||||
|
||||
@FunctionalInterface
|
||||
interface ThrowingConsumer<T> {
|
||||
@SuppressWarnings("java:S112") // we need to throw Exception here
|
||||
void consume(T item) throws Exception;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,9 +32,14 @@ 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;
|
||||
@@ -75,6 +80,41 @@ class TypedStoreContextTest {
|
||||
assertThat(ref.get().value).isEqualTo("wow");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSetContextClassLoader() {
|
||||
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
|
||||
ClassLoader classLoader = new URLClassLoader(new URL[0], contextClassLoader);
|
||||
|
||||
TypedStoreParameters<Sample> params = params(Sample.class);
|
||||
when(params.getClassLoader()).thenReturn(Optional.of(classLoader));
|
||||
|
||||
TypedStoreContext<Sample> context = TypedStoreContext.of(params);
|
||||
|
||||
AtomicReference<ClassLoader> 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(@TempDirectory.TempDir Path tempDir) {
|
||||
TypedStoreParameters<SampleWithAdapter> params = params(SampleWithAdapter.class);
|
||||
when(params.getAdapters()).thenReturn(Collections.singleton(new AppendingAdapter("!")));
|
||||
|
||||
TypedStoreContext<SampleWithAdapter> 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 <T> TypedStoreContext<T> context(Class<T> type) {
|
||||
return TypedStoreContext.of(params(type));
|
||||
}
|
||||
@@ -98,4 +138,37 @@ class TypedStoreContextTest {
|
||||
}
|
||||
}
|
||||
|
||||
@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<String, String> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user