Skip to content

Commit c74e01f

Browse files
SdkComponent: Add optional qualifier field
This qualifier enables having several components implementations for the same SDK version and with the same component type. You can choose the one you want by indicating its qualifier. The qualifier defaults to the empty string, so this is backwards compatible for existing code. The behaviour will be the same if you don't have components with the same version and type. but if you do an exception will be thrown instead of returning arbitrarily one of the component implementation. Update and add unit tests to check the expected behaviour.
1 parent e8a655a commit c74e01f

8 files changed

Lines changed: 123 additions & 17 deletions

File tree

src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponent.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@
1313
public String[] versions();
1414

1515
public SdkComponentType componentType();
16+
17+
public String qualifier() default "";
1618
}

src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentDescriptor.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,20 @@ public class SdkComponentDescriptor<T> implements Serializable {
2323

2424
private SdkComponentType componentType;
2525

26+
private String qualifier;
27+
2628
private Class<T> implType;
2729

2830
public SdkComponentDescriptor(String sdkVersion, SdkComponentType componentType,
2931
Class<T> implType) {
32+
this(sdkVersion, componentType, "", implType);
33+
}
34+
35+
public SdkComponentDescriptor(String sdkVersion, SdkComponentType componentType, String qualifier,
36+
Class<T> implType) {
3037
this.sdkVersion = Validate.notBlank(sdkVersion, "Undefined SDK version");
3138
this.componentType = Validate.notNull(componentType, "Undefined component type");
39+
this.qualifier = Validate.notNull(qualifier, "Undefined qualifier");
3240
this.implType = Validate.notNull(implType, "Undefined implementation type");
3341
}
3442

@@ -94,7 +102,7 @@ private boolean constructorHasExpectedParameters(Constructor<?> constructor,
94102

95103
@Override
96104
public int hashCode() {
97-
return Objects.hash(sdkVersion, componentType, implType);
105+
return Objects.hash(componentType, sdkVersion, qualifier, implType);
98106
}
99107

100108
@Override
@@ -106,8 +114,9 @@ public boolean equals(Object obj) {
106114
if (getClass() != obj.getClass())
107115
return false;
108116
SdkComponentDescriptor<?> other = (SdkComponentDescriptor<?>) obj;
109-
return componentType == other.componentType
117+
return componentType == other.componentType
110118
&& Objects.equals(sdkVersion, other.sdkVersion)
119+
&& Objects.equals(qualifier, other.qualifier)
111120
&& Objects.equals(implType, other.implType);
112121
}
113122

src/main/java/eu/europa/ted/eforms/sdk/component/SdkComponentFactory.java

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
import java.text.MessageFormat;
44
import java.util.Arrays;
55
import java.util.Collections;
6-
import java.util.EnumMap;
76
import java.util.HashMap;
87
import java.util.Map;
8+
import java.util.Objects;
99
import java.util.Optional;
1010
import java.util.stream.Stream;
1111
import org.reflections.Reflections;
@@ -20,7 +20,40 @@
2020
public abstract class SdkComponentFactory {
2121
private static final Logger logger = LoggerFactory.getLogger(SdkComponentFactory.class);
2222

23-
private Map<String, Map<SdkComponentType, SdkComponentDescriptor<?>>> componentsMap;
23+
private Map<String, Map<ComponentSelector, SdkComponentDescriptor<?>>> componentsMap;
24+
25+
class ComponentSelector {
26+
private final SdkComponentType componentType;
27+
private final String qualifier;
28+
29+
public ComponentSelector(SdkComponentType componentType, String qualifier) {
30+
this.componentType = componentType;
31+
this.qualifier = qualifier;
32+
}
33+
34+
@Override
35+
public int hashCode() {
36+
return Objects.hash(componentType, qualifier);
37+
}
38+
39+
@Override
40+
public boolean equals(Object obj) {
41+
if (this == obj)
42+
return true;
43+
if (obj == null)
44+
return false;
45+
if (getClass() != obj.getClass())
46+
return false;
47+
ComponentSelector other = (ComponentSelector) obj;
48+
if (!getEnclosingInstance().equals(other.getEnclosingInstance()))
49+
return false;
50+
return componentType == other.componentType && Objects.equals(qualifier, other.qualifier);
51+
}
52+
53+
private SdkComponentFactory getEnclosingInstance() {
54+
return SdkComponentFactory.this;
55+
}
56+
}
2457

2558
protected SdkComponentFactory() {
2659
populateComponents();
@@ -54,6 +87,8 @@ private void populateComponents() {
5487

5588
String[] supportedSdkVersions = annotation.versions();
5689
SdkComponentType componentType = annotation.componentType();
90+
String qualifier = annotation.qualifier();
91+
ComponentSelector selector = new ComponentSelector(componentType, qualifier);
5792

5893
logger.trace("Class [{}] has a component type of [{}] and supports SDK versions [{}]",
5994
clazz, componentType, supportedSdkVersions);
@@ -62,11 +97,11 @@ private void populateComponents() {
6297
SdkComponentDescriptor<?> component =
6398
new SdkComponentDescriptor<>(sdkVersion, componentType, clazz);
6499

65-
Map<SdkComponentType, SdkComponentDescriptor<?>> components =
100+
Map<ComponentSelector, SdkComponentDescriptor<?>> components =
66101
componentsMap.get(sdkVersion);
67102

68103
if (components != null) {
69-
SdkComponentDescriptor<?> existingComponent = components.get(componentType);
104+
SdkComponentDescriptor<?> existingComponent = components.get(selector);
70105

71106
if (existingComponent != null && !existingComponent.equals(component)) {
72107
throw new IllegalArgumentException(MessageFormat.format(
@@ -75,30 +110,39 @@ private void populateComponents() {
75110
clazz.getName()));
76111
}
77112
} else {
78-
components = new EnumMap<>(SdkComponentType.class);
113+
components = new HashMap<>();
79114
componentsMap.put(sdkVersion, components);
80115
}
81116

82-
components.put(componentType, component);
117+
components.put(selector, component);
83118
});
84119
});
85120
}
86121

87122
protected <T> T getComponentImpl(String sdkVersion, final SdkComponentType componentType,
88123
final Class<T> intf, Object... initArgs) throws InstantiationException {
89124

125+
return getComponentImpl(sdkVersion, componentType, "", intf, initArgs);
126+
}
127+
128+
protected <T> T getComponentImpl(String sdkVersion, final SdkComponentType componentType,
129+
final String qualifier, final Class<T> intf, Object... initArgs) throws InstantiationException {
130+
90131
String normalizedVersion = normalizeVersion(sdkVersion);
91132

133+
ComponentSelector selector = new ComponentSelector(componentType, qualifier);
134+
92135
@SuppressWarnings("unchecked")
93136
SdkComponentDescriptor<T> descriptor =
94137
(SdkComponentDescriptor<T>) Optional.ofNullable(componentsMap.get(normalizedVersion))
95-
.orElseGet(Collections::emptyMap).get(componentType);
138+
.orElseGet(Collections::emptyMap).get(selector);
96139

97140
if (descriptor == null) {
98141
logger.error("Failed to load required components of SDK [{}]", sdkVersion);
99142
throw new IllegalArgumentException(
100-
MessageFormat.format("No implementation found for component type [{0}] of SDK [{1}].",
101-
componentType, sdkVersion));
143+
MessageFormat.format(
144+
"No implementation found for SDK [{0}], component type [{1}] and qualifier '{2}'.",
145+
sdkVersion, componentType, qualifier));
102146
}
103147

104148
return descriptor.createInstance(initArgs);

src/test/java/eu/europa/ted/eforms/sdk/component/MyComponentFactory.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,10 @@ protected <T> T getComponentImpl(String sdkVersion, SdkComponentType componentTy
77
Object... initArgs) throws InstantiationException {
88
return super.getComponentImpl(sdkVersion, componentType, intf, initArgs);
99
}
10+
11+
@Override
12+
protected <T> T getComponentImpl(String sdkVersion, SdkComponentType componentType,
13+
String qualifier, Class<T> intf, Object... initArgs) throws InstantiationException {
14+
return super.getComponentImpl(sdkVersion, componentType, qualifier, intf, initArgs);
15+
}
1016
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package eu.europa.ted.eforms.sdk.component;
2+
3+
@SdkComponent(versions = "1", componentType = SdkComponentType.NODE, qualifier = "qualifier")
4+
public class NodeComponentWithQualifier implements TestComponent {
5+
6+
@Override
7+
public String testMethod() {
8+
return "Node";
9+
}
10+
}

src/test/java/eu/europa/ted/eforms/sdk/component/ScriptGeneratorB.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package eu.europa.ted.eforms.sdk.component;
22

3-
@SdkComponent(versions = "1", componentType = SdkComponentType.SCRIPT_GENERATOR)
3+
@SdkComponent(versions = "1", componentType = SdkComponentType.SCRIPT_GENERATOR, qualifier = "B")
44
class ScriptGeneratorB implements TestComponent {
55

66
@Override

src/test/java/eu/europa/ted/eforms/sdk/component/ScriptGeneratorSubclass.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package eu.europa.ted.eforms.sdk.component;
22

3+
@SdkComponent(versions = "1", componentType = SdkComponentType.SCRIPT_GENERATOR, qualifier = "other")
34
class ScriptGeneratorSubclass extends ScriptGeneratorA {
45

56
@Override

src/test/java/eu/europa/ted/eforms/sdk/component/SdkComponentFactoryTest.java

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package eu.europa.ted.eforms.sdk.component;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
46
import org.junit.jupiter.api.Test;
57

68
class SdkComponentFactoryTest {
@@ -16,12 +18,44 @@ void testGetComponentImpl() throws InstantiationException {
1618
@Test
1719
void testMultipleComponentImpl() throws InstantiationException {
1820
MyComponentFactory factory = new MyComponentFactory();
19-
TestComponent impl =
21+
22+
TestComponent implNoQualifier =
2023
factory.getComponentImpl("1.0", SdkComponentType.SCRIPT_GENERATOR, TestComponent.class);
2124

22-
// impl could be either ScriptGeneratorA, ScriptGeneratorB, or ScriptGeneratorSubclass
23-
// depending on which was the last found in SdkComponentFactory.populateComponents()
24-
assertEquals(ScriptGeneratorSubclass.class, impl.getClass());
25-
assertEquals("Subclass", impl.testMethod());
25+
// No qualifier specified, so we get an instance of ScriptGeneratorA
26+
assertEquals(ScriptGeneratorA.class, implNoQualifier.getClass());
27+
assertEquals("A", implNoQualifier.testMethod());
28+
29+
TestComponent implQualifierOther =
30+
factory.getComponentImpl("1.0", SdkComponentType.SCRIPT_GENERATOR, "other", TestComponent.class);
31+
32+
// Qualifier is "other", so we get an instance of ScriptGeneratorSubclass
33+
assertEquals(ScriptGeneratorSubclass.class, implQualifierOther.getClass());
34+
assertEquals("Subclass", implQualifierOther.testMethod());
35+
}
36+
37+
@Test
38+
void testComponentNotFound() throws InstantiationException {
39+
MyComponentFactory factory = new MyComponentFactory();
40+
41+
// No component for this version
42+
assertThrows(IllegalArgumentException.class, () ->
43+
factory.getComponentImpl("2.0", SdkComponentType.SCRIPT_GENERATOR, TestComponent.class));
44+
45+
// No component for this type
46+
assertThrows(IllegalArgumentException.class, () ->
47+
factory.getComponentImpl("1.0", SdkComponentType.CODELIST, TestComponent.class));
48+
49+
// No component for this qualifier
50+
assertThrows(IllegalArgumentException.class, () ->
51+
factory.getComponentImpl("1.0", SdkComponentType.SCRIPT_GENERATOR, "BAD", TestComponent.class));
52+
53+
// Only component for this version and type does not have a qualifier
54+
assertThrows(IllegalArgumentException.class, () ->
55+
factory.getComponentImpl("0.5", SdkComponentType.EFX_EXPRESSION_TRANSLATOR, "BAD", TestComponent.class));
56+
57+
// Only component for this version and type has a qualifier
58+
assertThrows(IllegalArgumentException.class, () ->
59+
factory.getComponentImpl("1.0", SdkComponentType.NODE, TestComponent.class));
2660
}
2761
}

0 commit comments

Comments
 (0)