diff --git a/resource/src/main/java/org/creekservice/api/platform/resource/ResourceInitializer.java b/resource/src/main/java/org/creekservice/api/platform/resource/ResourceInitializer.java
index 7c7ea5a..7f6aeca 100644
--- a/resource/src/main/java/org/creekservice/api/platform/resource/ResourceInitializer.java
+++ b/resource/src/main/java/org/creekservice/api/platform/resource/ResourceInitializer.java
@@ -58,42 +58,58 @@ public final class ResourceInitializer {
private static final StructuredLogger LOGGER =
StructuredLoggerFactory.internalLogger(ResourceInitializer.class);
- private final ResourceCreator resourceCreator;
+ private final Callbacks callbacks;
private final ComponentValidator componentValidator;
- /** Type for ensuring external resources exist */
- @FunctionalInterface
- public interface ResourceCreator {
+ /** Type for supplying callbacks to the resource initializer. */
+ public interface Callbacks {
/**
- * Get the handler for a specific type.
+ * Callback to validate that all the supplied {@code resources} consistently represent the
+ * same resource.
+ *
+ *
There is often multiple resource descriptors describing the same resource present in
+ * the system. For example, both input and output descriptors for the same resource. Any
+ * inconsistencies in the details of the resource between these descriptors can lead to
+ * bugs.
+ *
+ * @param type the type of the {@code resources}.
+ * @param resources the set of resources that all share the same {@link
+ * ResourceDescriptor#id()}.
+ * @param the resource descriptor type
+ * @throws RuntimeException with the details of any inconsistencies.
+ */
+ void validate(Class type, Collection resources);
+
+ /**
+ * Callback to ensure the supplied {@code creatableResources} exist.
*
* All {@code creatableResources} will be of the same type.
*
+ * @param type the type of the {@code creatableResources}.
* @param creatableResources the resource instances to ensure exists and are initialized.
* Resources passed will be {@link ResourceDescriptor#isCreatable creatable}.
* @param the creatable resource descriptor type
* @throws RuntimeException on unknown resource type
*/
void ensure(
- Collection creatableResources);
+ Class type, Collection creatableResources);
}
/**
* Create an initializer instance.
*
- * @param resourceCreator callback used to ensure external resources exist, as exposed by Creek
- * extensions.
+ * @param callbacks callbacks used to ensure external resources exist and are valid, as exposed
+ * by Creek extensions.
* @return an initializer instance.
*/
- public static ResourceInitializer resourceInitializer(final ResourceCreator resourceCreator) {
- return new ResourceInitializer(resourceCreator, new ComponentValidator());
+ public static ResourceInitializer resourceInitializer(final Callbacks callbacks) {
+ return new ResourceInitializer(callbacks, new ComponentValidator());
}
@VisibleForTesting
- ResourceInitializer(
- final ResourceCreator resourceCreator, final ComponentValidator componentValidator) {
- this.resourceCreator = requireNonNull(resourceCreator, "resourceCreator");
+ ResourceInitializer(final Callbacks callbacks, final ComponentValidator componentValidator) {
+ this.callbacks = requireNonNull(callbacks, "callbacks");
this.componentValidator = requireNonNull(componentValidator, "componentValidator");
}
@@ -113,8 +129,7 @@ public void init(final Collection extends ComponentDescriptor> components) {
ensureResources(
groupById(
components,
- resGroup -> resGroup.stream().anyMatch(ResourceDescriptor::isShared),
- false));
+ resGroup -> resGroup.stream().anyMatch(ResourceDescriptor::isShared)));
}
/**
@@ -132,8 +147,7 @@ public void service(final Collection extends ComponentDescriptor> components)
ensureResources(
groupById(
components,
- resGroup -> resGroup.stream().anyMatch(ResourceDescriptor::isOwned),
- true));
+ resGroup -> resGroup.stream().anyMatch(ResourceDescriptor::isOwned)));
}
/**
@@ -163,18 +177,15 @@ public void test(
resGroup ->
resGroup.stream().anyMatch(ResourceDescriptor::isUnowned)
&& resGroup.stream()
- .noneMatch(ResourceDescriptor::isOwned),
- true)
+ .noneMatch(ResourceDescriptor::isOwned))
.collect(Collectors.toMap(group -> group.get(0).id(), Function.identity()));
groupById(
otherComponents,
- resGroup -> resGroup.stream().anyMatch(r -> unowned.containsKey(r.id())),
- false)
+ resGroup -> resGroup.stream().anyMatch(r -> unowned.containsKey(r.id())))
.forEach(resGroup -> unowned.get(resGroup.get(0).id()).addAll(resGroup));
- final Stream> stream = unowned.values().stream();
- ensureResources(stream);
+ ensureResources(unowned.values().stream());
}
@SuppressWarnings({"unchecked", "rawtypes"})
@@ -184,7 +195,11 @@ private void ensureResources(final Stream> resGroups) {
.map(this::creatableDescriptor)
.collect(groupingBy(Object::getClass))
.values()
- .forEach(creatableResources -> resourceCreator.ensure((List) creatableResources));
+ .forEach(
+ creatableResources ->
+ callbacks.ensure(
+ (Class) creatableResources.get(0).getClass(),
+ (List) creatableResources));
}
private ResourceDescriptor creatableDescriptor(final List resGroup) {
@@ -196,17 +211,12 @@ private ResourceDescriptor creatableDescriptor(final List re
private Stream> groupById(
final Collection extends ComponentDescriptor> components,
- final Predicate> groupPredicate,
- final boolean validate) {
+ final Predicate> groupPredicate) {
final Map> grouped =
components.stream()
.flatMap(this::getResources)
.collect(groupingBy(ResourceDescriptor::id));
- if (validate) {
- grouped.values().forEach(this::validateResourceGroup);
- }
-
return grouped.values().stream().filter(groupPredicate);
}
@@ -221,14 +231,16 @@ private Stream getResources(final ComponentDescriptor compon
*
* @param resourceGroup the group of descriptors that describe the same resource.
*/
+ @SuppressWarnings("unchecked")
private void validateResourceGroup(final List resourceGroup) {
- if (isShared(resourceGroup.get(0))) {
+ final T first = resourceGroup.get(0);
+ if (isShared(first)) {
// if shared, all should be shared.
if (resourceGroup.stream().anyMatch(r -> !isShared(r))) {
throw new ResourceDescriptorMismatchInitializationException(
"shared", resourceGroup);
}
- } else if (isUnmanaged(resourceGroup.get(0))) {
+ } else if (isUnmanaged(first)) {
// if unmanaged, all should be unmanaged.
if (resourceGroup.stream().anyMatch(r -> !isUnmanaged(r))) {
throw new ResourceDescriptorMismatchInitializationException(
@@ -241,6 +253,8 @@ private void validateResourceGroup(final List
"owned or unowned", resourceGroup);
}
}
+
+ callbacks.validate((Class) first.getClass(), resourceGroup);
}
private static String formatResource(final List extends ResourceDescriptor> descriptors) {
diff --git a/resource/src/test/java/org/creekservice/api/platform/resource/ResourceInitializerTest.java b/resource/src/test/java/org/creekservice/api/platform/resource/ResourceInitializerTest.java
index d67532d..d5d8c4f 100644
--- a/resource/src/test/java/org/creekservice/api/platform/resource/ResourceInitializerTest.java
+++ b/resource/src/test/java/org/creekservice/api/platform/resource/ResourceInitializerTest.java
@@ -57,7 +57,7 @@ class ResourceInitializerTest {
@Mock private ComponentValidator validator;
@Mock private ComponentDescriptor component0;
@Mock private ComponentDescriptor component1;
- @Mock private ResourceInitializer.ResourceCreator resourceCreator;
+ @Mock private ResourceInitializer.Callbacks callbacks;
@Mock(extraInterfaces = SharedResource.class)
private ResourceA sharedResource1;
@@ -74,7 +74,7 @@ class ResourceInitializerTest {
@BeforeEach
void setUp() {
- initializer = new ResourceInitializer(resourceCreator, validator);
+ initializer = new ResourceInitializer(callbacks, validator);
when(sharedResource1.id()).thenReturn(A1_ID);
when(ownedResource1.id()).thenReturn(A1_ID);
@@ -224,6 +224,76 @@ void shouldThrowIfResourceGroupContainsUnownedAndOther() {
assertThat(e.getMessage(), containsString("sharedResource1"));
}
+ @SuppressWarnings("unchecked")
+ @Test
+ void shouldCallbackValidateEachResGroupOnInit() {
+ // Given:
+ final ResourceA sharedResource2 = resourceA(1, SharedResource.class);
+ when(component0.resources()).thenReturn(Stream.of(sharedResource1));
+ when(component1.resources()).thenReturn(Stream.of(sharedResource2));
+
+ // When:
+ initializer.init(List.of(component0, component1));
+
+ // Then:
+ verify(callbacks)
+ .validate(
+ (Class) sharedResource1.getClass(),
+ List.of(sharedResource1, sharedResource2));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ void shouldCallbackValidateEachResGroupOnService() {
+ // Given:
+ when(component0.resources()).thenReturn(Stream.of(ownedResource1));
+ when(component1.resources()).thenReturn(Stream.of(unownedResource1));
+
+ // When:
+ initializer.service(List.of(component0, component1));
+
+ // Then:
+ verify(callbacks)
+ .validate(
+ (Class) ownedResource1.getClass(),
+ List.of(ownedResource1, unownedResource1));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ void shouldCallbackValidateEachResGroupOnTest() {
+ // Given:
+ when(component0.resources()).thenReturn(Stream.of(unownedResource1));
+ when(component1.resources()).thenReturn(Stream.of(ownedResource1));
+
+ // When:
+ initializer.test(List.of(component0), List.of(component1));
+
+ // Then:
+ verify(callbacks)
+ .validate(
+ (Class) unownedResource1.getClass(),
+ List.of(unownedResource1, ownedResource1));
+ }
+
+ @Test
+ void shouldThrowIfValidateCallbackThrows() {
+ // Given:
+ when(component0.resources()).thenReturn(Stream.of(ownedResource1));
+ when(component1.resources()).thenReturn(Stream.of(unownedResource1));
+ final RuntimeException expected = new RuntimeException("BIG BADA BOOM");
+ doThrow(expected).when(callbacks).validate(any(), any());
+
+ // When:
+ final Exception e =
+ assertThrows(
+ RuntimeException.class,
+ () -> initializer.service(List.of(component0, component1)));
+
+ // Then:
+ assertThat(e, is(expected));
+ }
+
@Test
void shouldNotInitializeAnyResourceOnInitIfNoSharedResources() {
// Given:
@@ -234,7 +304,7 @@ void shouldNotInitializeAnyResourceOnInitIfNoSharedResources() {
initializer.init(List.of(component0, component1));
// Then:
- verify(resourceCreator, never()).ensure(any());
+ verify(callbacks, never()).ensure(any(), any());
}
@Test
@@ -249,7 +319,7 @@ void shouldNotInitializeAnyResourcesOnServiceIfNoOwnedResources() {
initializer.service(List.of(component0, component1));
// Then:
- verify(resourceCreator, never()).ensure(any());
+ verify(callbacks, never()).ensure(any(), any());
}
@Test
@@ -266,7 +336,7 @@ void shouldNotInitializeAnyResourcesOnTestIfNoUnownedResources() {
initializer.test(List.of(component0), List.of(component1));
// Then:
- verify(resourceCreator, never()).ensure(any());
+ verify(callbacks, never()).ensure(any(), any());
}
@Test
@@ -280,7 +350,7 @@ void shouldNotInitializeAnyResourcesOnTestIfNoUnownedResources() {
initializer.test(List.of(component0, component1), List.of());
// Then:
- verify(resourceCreator, never()).ensure(any());
+ verify(callbacks, never()).ensure(any(), any());
}
@Test
@@ -296,7 +366,7 @@ void shouldNotInitializeUnmanagedGroups() {
initializer.init(List.of(component0, component1));
// Then:
- verify(resourceCreator, never()).ensure(any());
+ verify(callbacks, never()).ensure(any(), any());
}
@Test
@@ -332,7 +402,10 @@ void shouldEnsureSharedResource() {
initializer.init(List.of(component0, component1));
// Then:
- verify(resourceCreator).ensure((List) List.of(sharedResource1, sharedResource2));
+ verify(callbacks)
+ .ensure(
+ (Class) sharedResource1.getClass(),
+ (List) List.of(sharedResource1, sharedResource2));
}
@SuppressWarnings({"unchecked", "rawtypes"})
@@ -347,7 +420,10 @@ void shouldEnsureOwnedResource() {
initializer.service(List.of(component0, component1));
// Then:
- verify(resourceCreator).ensure((List) List.of(ownedResource1, ownedResource2));
+ verify(callbacks)
+ .ensure(
+ (Class) ownedResource1.getClass(),
+ (List) List.of(ownedResource1, ownedResource2));
}
@SuppressWarnings({"unchecked", "rawtypes"})
@@ -363,14 +439,14 @@ void shouldEnsureUnownedResource() {
initializer.test(List.of(component0), List.of(component1));
// Then:
- verify(resourceCreator).ensure((List) List.of(ownedResource2));
+ verify(callbacks).ensure((Class) ownedResource2.getClass(), (List) List.of(ownedResource2));
}
@Test
void shouldThrowIfEnsureThrows() {
// Given:
final RuntimeException expected = new RuntimeException("boom");
- doThrow(expected).when(resourceCreator).ensure(any());
+ doThrow(expected).when(callbacks).ensure(any(), any());
when(component0.resources()).thenReturn(Stream.of(ownedResource1));
// When:
@@ -393,15 +469,15 @@ void shouldEnsureGroupingByHandler() {
initializer.service(List.of(component0));
// Then:
- verify(resourceCreator).ensure((List) List.of(ownedResource1));
- verify(resourceCreator).ensure((List) List.of(ownedResourceB));
+ verify(callbacks).ensure((Class) ownedResource1.getClass(), (List) List.of(ownedResource1));
+ verify(callbacks).ensure((Class) ownedResourceB.getClass(), (List) List.of(ownedResourceB));
}
@Test
void shouldThrowOnUnknownResourceType() {
// Given:
final NullPointerException expected = new NullPointerException("unknown");
- doThrow(expected).when(resourceCreator).ensure(any());
+ doThrow(expected).when(callbacks).ensure(any(), any());
when(component0.resources()).thenReturn(Stream.of(sharedResource1));
// When:
@@ -416,7 +492,7 @@ void shouldThrowOnUnknownResourceType() {
@Test
void shouldThrowOnInvalidComponentUsingActualValidator() {
// Given:
- initializer = ResourceInitializer.resourceInitializer(resourceCreator);
+ initializer = ResourceInitializer.resourceInitializer(callbacks);
// Then:
assertThrows(RuntimeException.class, () -> initializer.init(List.of(component0)));