Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 57 additions & 4 deletions src/main/java/org/dataloader/DataLoaderRegistry.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.dataloader;

import org.dataloader.annotations.PublicApi;
import org.dataloader.errors.StrictModeRegistryException;
import org.dataloader.impl.Assertions;
import org.dataloader.instrumentation.ChainedDataLoaderInstrumentation;
import org.dataloader.instrumentation.DataLoaderInstrumentation;
Expand All @@ -20,6 +21,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

import static java.lang.String.format;
import static org.dataloader.impl.Assertions.assertState;

/**
Expand All @@ -43,21 +45,28 @@
@PublicApi
@NullMarked
public class DataLoaderRegistry {

protected final Map<String, DataLoader<?, ?>> dataLoaders;
protected final @Nullable DataLoaderInstrumentation instrumentation;
protected final boolean strictMode;


public DataLoaderRegistry() {
this(new ConcurrentHashMap<>(), null);
this(new ConcurrentHashMap<>(), null, false);
}

private DataLoaderRegistry(Builder builder) {
this(builder.dataLoaders, builder.instrumentation);
this(builder.dataLoaders, builder.instrumentation, builder.strictMode);
}

protected DataLoaderRegistry(Map<String, DataLoader<?, ?>> dataLoaders, @Nullable DataLoaderInstrumentation instrumentation) {
protected DataLoaderRegistry(
Map<String, DataLoader<?, ?>> dataLoaders,
@Nullable DataLoaderInstrumentation instrumentation,
boolean strictMode
) {
this.dataLoaders = instrumentDLs(dataLoaders, instrumentation);
this.instrumentation = instrumentation;
this.strictMode = strictMode;
}

private Map<String, DataLoader<?, ?>> instrumentDLs(Map<String, DataLoader<?, ?>> incomingDataLoaders, @Nullable DataLoaderInstrumentation registryInstrumentation) {
Expand Down Expand Up @@ -144,6 +153,9 @@ private static DataLoaderOptions setInInstrumentation(DataLoaderOptions options,
*/
public DataLoaderRegistry register(DataLoader<?, ?> dataLoader) {
String name = Assertions.nonNull(dataLoader.getName(), () -> "The DataLoader must have a non null name");
if (strictMode) {
assertKeyStrictly(name);
}
dataLoaders.put(name, nameAndInstrumentDL(name, instrumentation, dataLoader));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to cover org.dataloader.DataLoaderRegistry#computeIfAbsent as well

I think this code would be better put in the nameAndInstrumentDL method as a common place

The following is all the places it is called

instrumentDLs(Map<String, DataLoader<?, ?>>, DataLoaderInstrumentation)
register(DataLoader<?, ?>)
register(String, DataLoader<?, ?>)
registerAndGet(String, DataLoader<?, ?>)
computeIfAbsent(String, Function<String, DataLoader<?, ?>>)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - as agreed elsewhere, I've omitted computeIfAbsent since this repeated invocations to the same key will not override the DataLoader with strict mode off.

I did have to make nameAndInstrumentDL to get access to strictMode and the DataLoader map, however.

return this;
}
Expand All @@ -160,6 +172,9 @@ public DataLoaderRegistry register(DataLoader<?, ?> dataLoader) {
* @return this registry
*/
public DataLoaderRegistry register(String key, DataLoader<?, ?> dataLoader) {
if (strictMode) {
assertKeyStrictly(key);
}
dataLoaders.put(key, nameAndInstrumentDL(key, instrumentation, dataLoader));
return this;
}
Expand All @@ -176,6 +191,9 @@ public DataLoaderRegistry register(String key, DataLoader<?, ?> dataLoader) {
* @return the data loader instance that was registered
*/
public <K, V> DataLoader<K, V> registerAndGet(String key, DataLoader<?, ?> dataLoader) {
if (strictMode) {
assertKeyStrictly(key);
}
dataLoaders.put(key, nameAndInstrumentDL(key, instrumentation, dataLoader));
return Objects.requireNonNull(getDataLoader(key));
}
Expand Down Expand Up @@ -214,7 +232,9 @@ public <K, V> DataLoader<K, V> computeIfAbsent(final String key,
* @return a new combined registry
*/
public DataLoaderRegistry combine(DataLoaderRegistry registry) {
DataLoaderRegistry combined = new DataLoaderRegistry();
DataLoaderRegistry combined = new Builder()
.strictMode(strictMode)
.build();

this.dataLoaders.forEach(combined::register);
registry.dataLoaders.forEach(combined::register);
Expand Down Expand Up @@ -312,6 +332,12 @@ public Statistics getStatistics() {
return stats;
}

protected void assertKeyStrictly(String key) {
if (dataLoaders.containsKey(key)) {
throw new StrictModeRegistryException(format("The key %s already has a DataLoader defined", key));
}
}

/**
* @return A builder of {@link DataLoaderRegistry}s
*/
Expand All @@ -323,6 +349,19 @@ public static class Builder {

private final Map<String, DataLoader<?, ?>> dataLoaders = new HashMap<>();
private @Nullable DataLoaderInstrumentation instrumentation;
private boolean strictMode;

/**
* This puts the builder into strict mode, so if things get defined twice, for example, it
* will throw a {@link org.dataloader.errors.StrictModeRegistryException}.
*
* @param strictMode whether strict mode is enabled
* @return this builder
*/
public Builder strictMode(boolean strictMode) {
this.strictMode = strictMode;
return this;
}

/**
* This will register a new dataloader
Expand All @@ -332,6 +371,9 @@ public static class Builder {
* @return this builder for a fluent pattern
*/
public Builder register(String key, DataLoader<?, ?> dataLoader) {
if (strictMode) {
assertKeyStrictly(key);
}
dataLoaders.put(key, dataLoader);
return this;
}
Expand All @@ -344,6 +386,11 @@ public Builder register(String key, DataLoader<?, ?> dataLoader) {
* @return this builder for a fluent pattern
*/
public Builder registerAll(DataLoaderRegistry otherRegistry) {
if (strictMode) {
otherRegistry.dataLoaders.forEach((key, dataLoader) -> {
assertKeyStrictly(key);
});
}
dataLoaders.putAll(otherRegistry.dataLoaders);
return this;
}
Expand All @@ -353,6 +400,12 @@ public Builder instrumentation(DataLoaderInstrumentation instrumentation) {
return this;
}

private void assertKeyStrictly(String key) {
if (dataLoaders.containsKey(key)) {
throw new StrictModeRegistryException(format("The key %s already has a DataLoader defined", key));
}
}

/**
* @return the newly built {@link DataLoaderRegistry}
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.dataloader.errors;

import org.dataloader.annotations.PublicApi;

/**
* An exception that is thrown when {@link org.dataloader.DataLoaderRegistry.Builder#strictMode(boolean)} is true and multiple
* DataLoaders are registered to the same key.
*/
@PublicApi
public class StrictModeRegistryException extends RuntimeException {
public StrictModeRegistryException(String msg) {
super(msg);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.dataloader.DataLoader;
import org.dataloader.DataLoaderRegistry;
import org.dataloader.annotations.ExperimentalApi;
import org.dataloader.errors.StrictModeRegistryException;
import org.dataloader.impl.Assertions;
import org.dataloader.instrumentation.DataLoaderInstrumentation;
import org.jspecify.annotations.NullMarked;
Expand All @@ -16,6 +17,7 @@
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import static java.lang.String.format;
import static org.dataloader.impl.Assertions.nonNull;

/**
Expand Down Expand Up @@ -69,7 +71,7 @@ public class ScheduledDataLoaderRegistry extends DataLoaderRegistry implements A
private volatile boolean closed;

private ScheduledDataLoaderRegistry(Builder builder) {
super(builder.dataLoaders, builder.instrumentation);
super(builder.dataLoaders, builder.instrumentation, builder.strictMode);
this.scheduledExecutorService = Assertions.nonNull(builder.scheduledExecutorService);
this.defaultExecutorUsed = builder.defaultExecutorUsed;
this.schedule = builder.schedule;
Expand Down Expand Up @@ -120,7 +122,8 @@ public boolean isTickerMode() {
*/
public ScheduledDataLoaderRegistry combine(DataLoaderRegistry registry) {
Builder combinedBuilder = ScheduledDataLoaderRegistry.newScheduledRegistry()
.dispatchPredicate(this.dispatchPredicate);
.dispatchPredicate(this.dispatchPredicate)
.strictMode(this.strictMode);
combinedBuilder.registerAll(this);
combinedBuilder.registerAll(registry);
return combinedBuilder.build();
Expand Down Expand Up @@ -166,6 +169,9 @@ public DispatchPredicate getDispatchPredicate() {
* @return this registry
*/
public ScheduledDataLoaderRegistry register(String key, DataLoader<?, ?> dataLoader, DispatchPredicate dispatchPredicate) {
if (strictMode) {
assertKeyStrictly(key);
}
dataLoaders.put(key, dataLoader);
dataLoaderPredicates.put(dataLoader, dispatchPredicate);
return this;
Expand Down Expand Up @@ -272,6 +278,7 @@ public static class Builder {
private Duration schedule = Duration.ofMillis(10);
private boolean tickerMode = false;
private @Nullable DataLoaderInstrumentation instrumentation;
private boolean strictMode;


/**
Expand All @@ -291,6 +298,18 @@ public Builder schedule(Duration schedule) {
return this;
}

/**
* This puts the builder into strict mode, so if things get defined twice, for example, it
* will throw a {@link org.dataloader.errors.StrictModeRegistryException}.
*
* @param strictMode whether strict mode is enabled
* @return this builder
*/
public Builder strictMode(boolean strictMode) {
this.strictMode = strictMode;
return this;
}

/**
* This will register a new dataloader
*
Expand All @@ -299,6 +318,9 @@ public Builder schedule(Duration schedule) {
* @return this builder for a fluent pattern
*/
public Builder register(String key, DataLoader<?, ?> dataLoader) {
if (strictMode) {
assertKeyStrictly(key);
}
dataLoaders.put(key, dataLoader);
return this;
}
Expand Down Expand Up @@ -326,7 +348,13 @@ public Builder register(String key, DataLoader<?, ?> dataLoader, DispatchPredica
* @return this builder for a fluent pattern
*/
public Builder registerAll(DataLoaderRegistry otherRegistry) {
dataLoaders.putAll(otherRegistry.getDataLoadersMap());
Map<String, DataLoader<?, ?>> otherDataLoaders = otherRegistry.getDataLoadersMap();
if (strictMode) {
otherDataLoaders.forEach((key, dataLoader) -> {
assertKeyStrictly(key);
});
}
dataLoaders.putAll(otherDataLoaders);
if (otherRegistry instanceof ScheduledDataLoaderRegistry) {
ScheduledDataLoaderRegistry other = (ScheduledDataLoaderRegistry) otherRegistry;
dataLoaderPredicates.putAll(other.dataLoaderPredicates);
Expand Down Expand Up @@ -364,6 +392,12 @@ public Builder instrumentation(DataLoaderInstrumentation instrumentation) {
return this;
}

private void assertKeyStrictly(String key) {
if (dataLoaders.containsKey(key)) {
throw new StrictModeRegistryException(format("The key %s already has a DataLoader defined", key));
}
}

/**
* @return the newly built {@link ScheduledDataLoaderRegistry}
*/
Expand Down
46 changes: 46 additions & 0 deletions src/test/java/org/dataloader/DataLoaderRegistryTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.dataloader;

import org.dataloader.errors.StrictModeRegistryException;
import org.dataloader.stats.SimpleStatisticsCollector;
import org.dataloader.stats.Statistics;
import org.junit.jupiter.api.Assertions;
Expand All @@ -14,6 +15,7 @@
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class DataLoaderRegistryTest {
final BatchLoader<Object, Object> identityBatchLoader = CompletableFuture::completedFuture;
Expand Down Expand Up @@ -219,4 +221,48 @@ public void builder_works() {
assertThat(registry.getDataLoader("c"), equalTo(dlC));

}

@Test
public void strictMode_works() {

DataLoader<Object, Object> dlA = newDataLoader(identityBatchLoader);
DataLoader<Object, Object> dlB = newDataLoader(identityBatchLoader);

assertThrows(StrictModeRegistryException.class, () -> {
DataLoaderRegistry.newRegistry()
.strictMode(true)
.register("a", dlA)
.register("a", dlB)
.build();
});
assertThrows(StrictModeRegistryException.class, () -> {
DataLoaderRegistry.newRegistry()
.strictMode(true)
.register("a", dlA)
.registerAll(DataLoaderRegistry.newRegistry()
.register("a", dlB)
.build())
.build();
});

DataLoaderRegistry registry = DataLoaderRegistry.newRegistry()
.strictMode(true)
.build();
registry.register("a", dlA);

assertThrows(StrictModeRegistryException.class, () -> {
registry.register("a", dlB);
});
assertThrows(StrictModeRegistryException.class, () -> {
registry.register(newDataLoader("a", identityBatchLoader));
});
assertThrows(StrictModeRegistryException.class, () -> {
registry.registerAndGet("a", dlB);
});
assertThrows(StrictModeRegistryException.class, () -> {
registry.combine(DataLoaderRegistry.newRegistry()
.register("a", dlB)
.build());
});
}
}
Loading