diff --git a/core/src/main/java/org/wildfly/glow/AnnotationFieldValue.java b/core/src/main/java/org/wildfly/glow/AnnotationFieldValue.java new file mode 100644 index 00000000..f76c57e4 --- /dev/null +++ b/core/src/main/java/org/wildfly/glow/AnnotationFieldValue.java @@ -0,0 +1,52 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.wildfly.glow; + +/** + * + * @author jdenise + */ +public class AnnotationFieldValue { + + private final String annotation; + private final String fieldName; + private final String fieldValue; + private final Layer layer; + + public AnnotationFieldValue(String annotation, String fieldName, String fieldValue, Layer layer) { + this.annotation = annotation; + this.fieldName = fieldName; + this.fieldValue = fieldValue; + this.layer = layer; + } + + /** + * @return the annotation + */ + public String getAnnotation() { + return annotation; + } + + /** + * @return the fieldName + */ + public String getFieldName() { + return fieldName; + } + + /** + * @return the fieldValue + */ + public String getFieldValue() { + return fieldValue; + } + + /** + * @return the layer + */ + public Layer getLayer() { + return layer; + } +} diff --git a/core/src/main/java/org/wildfly/glow/DataSourceDefinitionInfo.java b/core/src/main/java/org/wildfly/glow/DataSourceDefinitionInfo.java new file mode 100644 index 00000000..26f8e0dc --- /dev/null +++ b/core/src/main/java/org/wildfly/glow/DataSourceDefinitionInfo.java @@ -0,0 +1,47 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.wildfly.glow; + + +public class DataSourceDefinitionInfo { + private final String name; + private final String url; + private final String className; + private final Layer driverLayer; + public DataSourceDefinitionInfo(String name, String url, String className, Layer driverLayer) { + this.name = name; + this.url = url; + this.className = className; + this.driverLayer = driverLayer; + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @return the url + */ + public String getUrl() { + return url; + } + + /** + * @return the className + */ + public String getClassName() { + return className; + } + + /** + * @return the driverLayer + */ + public Layer getDriverLayer() { + return driverLayer; + } +} diff --git a/core/src/main/java/org/wildfly/glow/DeploymentScanner.java b/core/src/main/java/org/wildfly/glow/DeploymentScanner.java index e7b7bffa..f7168633 100644 --- a/core/src/main/java/org/wildfly/glow/DeploymentScanner.java +++ b/core/src/main/java/org/wildfly/glow/DeploymentScanner.java @@ -61,6 +61,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.TreeSet; @@ -139,7 +140,7 @@ public void scan(LayerMapping mapping, Set layers, Map all } } - errorSession.collectEndOfScanErrors(verbose, ctx.resourceInjectionJndiInfos, ctx.contextLookupInfos, ctx.allClasses); + errorSession.collectEndOfScanErrors(verbose, ctx.resourceInjectionJndiInfos, ctx.contextLookupInfos, ctx.dataSourceDefinitionInfos, ctx.allClasses); } private void scan(DeploymentScanContext ctx) throws Exception { @@ -193,6 +194,34 @@ private void scanAnnotations(DeploymentScanContext ctx) throws IOException { } } } + Map> fields = ctx.mapping.getAnnotationFieldValues().get(ai.name().toString()); + if (fields != null) { + Layer foundLayer = null; + for(Entry> f : fields.entrySet()) { + String val = getAnnotationValue(ai, f.getKey()); + if (val != null) { + List lstFields = f.getValue(); + for (AnnotationFieldValue fv : lstFields) { + if (Utils.isPattern(fv.getFieldValue())) { + Pattern p = Pattern.compile(fv.getFieldValue()); + if (p.matcher(val).matches()) { + foundLayer = fv.getLayer(); + LayerMapping.addRule(LayerMapping.RULE.ANNOTATION_VALUE, foundLayer, ai.name().toString() + "_" + f.getKey() + "=" + fv.getFieldValue()); + ctx.layers.add(fv.getLayer()); + } + } else { + if (val.equals(fv.getFieldValue())) { + foundLayer = fv.getLayer(); + LayerMapping.addRule(LayerMapping.RULE.ANNOTATION_VALUE, foundLayer, ai.name().toString() + "_" + f.getKey() + "=" + fv.getFieldValue()); + ctx.layers.add(fv.getLayer()); + } + } + } + } + } + // DataSourceDefinition are only added based on layers discovered in the above nested loop. + handleDataSourceDefinitionAnnotations(ai, ctx, foundLayer); + } } } } @@ -260,6 +289,18 @@ private void handleResourceInjectionAnnotations(AnnotationInstance annotationIns } } + private void handleDataSourceDefinitionAnnotations(AnnotationInstance annotationInstance, DeploymentScanContext ctx, Layer foundLayer) { + if (annotationInstance.name().toString().equals("jakarta.annotation.sql.DataSourceDefinition")) { + String name = getAnnotationValue(annotationInstance, "name"); + String url = getAnnotationValue(annotationInstance, "url"); + String className = getAnnotationValue(annotationInstance, "className"); + if (name != null) { + DataSourceDefinitionInfo di = new DataSourceDefinitionInfo(name, url, className, foundLayer); + ctx.dataSourceDefinitionInfos.put(name, di); + } + } + } + private boolean isSetter(MethodInfo methodInfo) { return methodInfo.name().startsWith("set") && methodInfo.parameterTypes().size() == 1; } @@ -840,6 +881,7 @@ static class DeploymentScanContext { private final ErrorIdentificationSession errorSession; private final Set allClasses = new HashSet<>(); private final Map resourceInjectionJndiInfos = new HashMap<>(); + private final Map dataSourceDefinitionInfos = new HashMap<>(); public Set contextLookupInfos = new HashSet<>(); private DeploymentScanContext(LayerMapping mapping, Set layers, Map allLayers, ErrorIdentificationSession errorSession) { diff --git a/core/src/main/java/org/wildfly/glow/LayerMapping.java b/core/src/main/java/org/wildfly/glow/LayerMapping.java index 50a72e59..40ef0b67 100644 --- a/core/src/main/java/org/wildfly/glow/LayerMapping.java +++ b/core/src/main/java/org/wildfly/glow/LayerMapping.java @@ -19,6 +19,7 @@ import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; @@ -35,6 +36,7 @@ public enum RULE { ADD_ON_ALWAYS_INCLUDED, ALWAYS_INCLUDED, ANNOTATION, + ANNOTATION_VALUE, BASE_LAYER, BRING_DATASOURCE, EXPECTED_FILE, @@ -48,6 +50,7 @@ public enum RULE { } private final Map> constantPoolClassInfos = new HashMap<>(); private final Map> annotations = new HashMap<>(); + private final Map>> annotationFieldValues = new HashMap<>(); private final Map activeProfilesLayers = new HashMap<>(); private final Map> allProfilesLayers = new HashMap<>(); private Layer defaultBaseLayer; @@ -176,6 +179,10 @@ public Map getHiddenConditions() { return hiddenConditions; } + public Map>> getAnnotationFieldValues() { + return annotationFieldValues; + } + public static String cleanupKey(String key) { if (key.startsWith(LayerMetadata.HIDDEN_IF)) { key = key.substring(key.indexOf(LayerMetadata.HIDDEN_IF) + LayerMetadata.HIDDEN_IF.length() + 1, key.length()); diff --git a/core/src/main/java/org/wildfly/glow/LayerMetadata.java b/core/src/main/java/org/wildfly/glow/LayerMetadata.java index 430493ec..d5416777 100644 --- a/core/src/main/java/org/wildfly/glow/LayerMetadata.java +++ b/core/src/main/java/org/wildfly/glow/LayerMetadata.java @@ -34,6 +34,7 @@ public abstract class LayerMetadata { public static final String ADD_ON_DESCRIPTION = PREFIX + "add-on-description"; public static final String ADD_ON_FIX = PREFIX + "add-on-fix-"; public static final String ANNOTATIONS = PREFIX + "annotations"; + public static final String ANNOTATION_FIELD_VALUE = PREFIX + "annotation.field.value"; public static final String BRING_DATASOURCE = PREFIX + "bring-datasource"; public static final String CLASS = PREFIX + "class"; public static final String CONFIGURATION = PREFIX + "configuration"; @@ -67,6 +68,7 @@ public abstract class LayerMetadata { FULLY_NAMED_RULES.add(KIND); RULES_WITH_SUFFIX.add(ADD_ON_FIX); + RULES_WITH_SUFFIX.add(ANNOTATION_FIELD_VALUE); RULES_WITH_SUFFIX.add(EXPECTED_FILE); RULES_WITH_SUFFIX.add(NOT_EXPECTED_FILE); RULES_WITH_SUFFIX.add(PROFILE); diff --git a/core/src/main/java/org/wildfly/glow/Utils.java b/core/src/main/java/org/wildfly/glow/Utils.java index 28d21691..de4b8a2f 100644 --- a/core/src/main/java/org/wildfly/glow/Utils.java +++ b/core/src/main/java/org/wildfly/glow/Utils.java @@ -46,6 +46,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -417,6 +418,22 @@ public static LayerMapping buildMapping(Map layers, Set p } continue; } + if (k.startsWith(LayerMetadata.ANNOTATION_FIELD_VALUE)) { + String val = l.getProperties().get(k); + int i = val.indexOf(","); + String annotation = val.substring(0, i); + String annotationFieldValue = val.substring(i+1); + int j = annotationFieldValue.indexOf("="); + String field = annotationFieldValue.substring(0,j); + String fieldValue = annotationFieldValue.substring(j+1); + if (Utils.isPattern(fieldValue)) { + fieldValue = Utils.escapePattern(fieldValue); + } + Map> ll = mapping.getAnnotationFieldValues().computeIfAbsent(annotation, value -> new HashMap<>()); + List lst = ll.computeIfAbsent(field, value -> new ArrayList<>()); + lst.add(new AnnotationFieldValue(annotation, field, fieldValue, l)); + continue; + } if (LayerMetadata.CLASS.equals(k)) { String val = l.getProperties().get(k); String[] split = val.split(","); diff --git a/core/src/main/java/org/wildfly/glow/error/DatasourceErrorIdentification.java b/core/src/main/java/org/wildfly/glow/error/DatasourceErrorIdentification.java index a98614cd..40acfdbb 100644 --- a/core/src/main/java/org/wildfly/glow/error/DatasourceErrorIdentification.java +++ b/core/src/main/java/org/wildfly/glow/error/DatasourceErrorIdentification.java @@ -37,6 +37,7 @@ import java.util.Set; import java.util.TreeSet; import java.util.regex.Pattern; +import org.wildfly.glow.DataSourceDefinitionInfo; import org.wildfly.glow.Env; import static org.wildfly.glow.Utils.getAddOnFix; @@ -51,9 +52,11 @@ public class DatasourceErrorIdentification implements ErrorIdentification { private static final String UNBOUND_DATASOURCES_ERROR = "unbound-datasources"; private static final String NO_DEFAULT_DATASOURCE_ERROR = "no-default-datasource"; + private static final String UNKNOWN_DRIVER_ERROR = "unknown-driver"; private static final String UNBOUND_DATASOURCES_ERROR_DESCRIPTION = "unbound datasources error"; private static final String NO_DEFAULT_DATASOURCE_ERROR_DESCRIPTION = "no default datasource found error"; Map> errors = new HashMap<>(); + private Map datasourceDefinitionInfos; @Override public void collectErrors(Path rootPath) throws Exception { @@ -130,32 +133,42 @@ public Map> refreshErrors(Set allBaseLayers) throws Excep if (unboundDatasourcesErrors != null) { for (IdentifiedError error : unboundDatasourcesErrors) { UnboundDatasourceError uds = (UnboundDatasourceError) error; - for (Layer l : allBaseLayers) { - if (l.getBringDatasources().contains(uds.unboundDatasource)) { - // The error is directly handled, we can remove it. - toRemove.add(uds.unboundDatasource); - break; - } else { - if (l.getAddOn() != null) { - Fix fix = l.getAddOn().getFixes().get(error.getId()); - if (fix != null) { - String content = null; - if (!l.getBringDatasources().contains(uds.unboundDatasource)) { - content = fix.getContent(); - if (content != null) { - content = content.replaceAll("##ITEM##", uds.unboundDatasource); - } - if (fix.isEnv()) { - Set envs = ret.get(l); - if (envs == null) { - envs = new HashSet<>(); - ret.put(l, envs); + if (datasourceDefinitionInfos.containsKey(uds.unboundDatasource)) { + toRemove.add(uds.unboundDatasource); + DataSourceDefinitionInfo di = datasourceDefinitionInfos.get(uds.unboundDatasource); + if (di.getDriverLayer() == null) { + Set errs = errors.computeIfAbsent(UNKNOWN_DRIVER_ERROR, res -> new HashSet<>()); + errs.add(new IdentifiedError("Unknown driver", "The driver located in the URL [" + di.getUrl() + "] for the datasource " + uds.unboundDatasource + + " injected thanks to the jakarta.annotation.sql.DataSourceDefinition annotation is not known.", ErrorLevel.WARN)); + } + } else { + for (Layer l : allBaseLayers) { + if (l.getBringDatasources().contains(uds.unboundDatasource)) { + // The error is directly handled, we can remove it. + toRemove.add(uds.unboundDatasource); + break; + } else { + if (l.getAddOn() != null) { + Fix fix = l.getAddOn().getFixes().get(error.getId()); + if (fix != null) { + String content = null; + if (!l.getBringDatasources().contains(uds.unboundDatasource)) { + content = fix.getContent(); + if (content != null) { + content = content.replaceAll("##ITEM##", uds.unboundDatasource); + } + if (fix.isEnv()) { + Set envs = ret.get(l); + if (envs == null) { + envs = new HashSet<>(); + ret.put(l, envs); + } + envs.add(new Env(fix.getEnvName(), Fix.getEnvValue(content), false, true, false)); } - envs.add(new Env(fix.getEnvName(), Fix.getEnvValue(content), false, true, false)); } + String errorMessage = getAddOnFix(l.getAddOn(), content); + error.setFixed(errorMessage); } - String errorMessage = getAddOnFix(l.getAddOn(), content); - error.setFixed(errorMessage); } } } @@ -203,4 +216,8 @@ public List getErrors() { } return ret; } + + void setDataSourceDefinitionInfos(Map datasourceDefinitionInfos) { + this.datasourceDefinitionInfos = datasourceDefinitionInfos; + } } diff --git a/core/src/main/java/org/wildfly/glow/error/ErrorIdentificationSession.java b/core/src/main/java/org/wildfly/glow/error/ErrorIdentificationSession.java index 62d1e1f5..2e46edd5 100644 --- a/core/src/main/java/org/wildfly/glow/error/ErrorIdentificationSession.java +++ b/core/src/main/java/org/wildfly/glow/error/ErrorIdentificationSession.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.wildfly.glow.DataSourceDefinitionInfo; import org.wildfly.glow.Env; /** @@ -52,8 +53,10 @@ public void collectEndOfScanErrors( boolean verbose, Map resourceInjectionInfos, Set initialContextLookupInfos, + Map datasourceDefinitionInfos, Set allClasses) { jndiErrorIdentification.collectErrors(verbose, resourceInjectionInfos, initialContextLookupInfos, allClasses); + ds.setDataSourceDefinitionInfos(datasourceDefinitionInfos); } public Map> refreshErrors(Set allBaseLayers, LayerMapping mapping, Set enabledAddOns) throws Exception { diff --git a/tests/glow-tests/src/test/java/org/wildfly/glow/test/core/datasource/DataSourceDefinitionTestCase.java b/tests/glow-tests/src/test/java/org/wildfly/glow/test/core/datasource/DataSourceDefinitionTestCase.java new file mode 100644 index 00000000..09712b47 --- /dev/null +++ b/tests/glow-tests/src/test/java/org/wildfly/glow/test/core/datasource/DataSourceDefinitionTestCase.java @@ -0,0 +1,45 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.glow.test.core.datasource; + +import jakarta.annotation.sql.DataSourceDefinition; +import org.junit.Assert; +import org.junit.Test; +import org.wildfly.glow.ScanResults; +import org.wildfly.glow.error.IdentifiedError; +import org.wildfly.glow.test.core.TestPackager; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.junit.Ignore; + +/** + * Tests that timer service etc. are considered strongly typed + */ +@DataSourceDefinition(name="java:jboss/datasources/batch-processingDS", + className="org.h2.jdbcx.JdbcDataSource", + url="jdbc:h2:mem:batch-processing;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1", + user="sa", + password="sa" +) +// Will un-ignore this test for WildFly 35 Final +@Ignore +public class DataSourceDefinitionTestCase { + private final TestPackager testPackager = new TestPackager(); + + @Test + public void h2DriverUsage() throws Exception { + ScanResults scanResults = testPackager.packageTestAsArchiveAndScan(DataSourceDefinitionTestCase.class); + List errors = scanResults.getErrorSession().getErrors(); + Assert.assertEquals(0, errors.size()); + + Set layers = scanResults.getDiscoveredLayers().stream().map(l -> l.getName()).collect(Collectors.toSet()); + Assert.assertTrue(layers.contains("h2-driver")); + + } + +} diff --git a/tests/test-feature-pack/galleon-pack/src/main/resources/layers/standalone/annotation-field-value/layer-spec.xml b/tests/test-feature-pack/galleon-pack/src/main/resources/layers/standalone/annotation-field-value/layer-spec.xml new file mode 100644 index 00000000..d768fe26 --- /dev/null +++ b/tests/test-feature-pack/galleon-pack/src/main/resources/layers/standalone/annotation-field-value/layer-spec.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/tests/test-feature-pack/galleon-pack/src/main/resources/layers/standalone/annotation-field-value2/layer-spec.xml b/tests/test-feature-pack/galleon-pack/src/main/resources/layers/standalone/annotation-field-value2/layer-spec.xml new file mode 100644 index 00000000..31a0b51b --- /dev/null +++ b/tests/test-feature-pack/galleon-pack/src/main/resources/layers/standalone/annotation-field-value2/layer-spec.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/tests/test-feature-pack/layer-metadata-tests/src/test/java/org/wildfly/glow/rules/test/annotation/field/value/FieldValueAnnotationTestCase.java b/tests/test-feature-pack/layer-metadata-tests/src/test/java/org/wildfly/glow/rules/test/annotation/field/value/FieldValueAnnotationTestCase.java new file mode 100644 index 00000000..4a9fb8b0 --- /dev/null +++ b/tests/test-feature-pack/layer-metadata-tests/src/test/java/org/wildfly/glow/rules/test/annotation/field/value/FieldValueAnnotationTestCase.java @@ -0,0 +1,23 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.glow.rules.test.annotation.field.value; + +import org.junit.Test; +import org.wildfly.glow.rules.test.AbstractLayerMetaDataTestCase; + +public class FieldValueAnnotationTestCase extends AbstractLayerMetaDataTestCase { + + @Test + public void testValueAnnotationUsage() { + testSingleClassWar(FieldValueAnnotationUsage.class, "annotation-field-value"); + } + + @Test + public void testValueAnnotationUsage2() { + testSingleClassWar(FieldValueAnnotationUsage2.class, "annotation-field-value2"); + } + +} diff --git a/tests/test-feature-pack/layer-metadata-tests/src/test/java/org/wildfly/glow/rules/test/annotation/field/value/FieldValueAnnotationUsage.java b/tests/test-feature-pack/layer-metadata-tests/src/test/java/org/wildfly/glow/rules/test/annotation/field/value/FieldValueAnnotationUsage.java new file mode 100644 index 00000000..35d33a40 --- /dev/null +++ b/tests/test-feature-pack/layer-metadata-tests/src/test/java/org/wildfly/glow/rules/test/annotation/field/value/FieldValueAnnotationUsage.java @@ -0,0 +1,12 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.glow.rules.test.annotation.field.value; + +import org.wildfly.glow.test.rules.classes.annotation.field.value.FieldValue; + +@FieldValue(prop = "foo bar") +public class FieldValueAnnotationUsage { +} diff --git a/tests/test-feature-pack/layer-metadata-tests/src/test/java/org/wildfly/glow/rules/test/annotation/field/value/FieldValueAnnotationUsage2.java b/tests/test-feature-pack/layer-metadata-tests/src/test/java/org/wildfly/glow/rules/test/annotation/field/value/FieldValueAnnotationUsage2.java new file mode 100644 index 00000000..8d650779 --- /dev/null +++ b/tests/test-feature-pack/layer-metadata-tests/src/test/java/org/wildfly/glow/rules/test/annotation/field/value/FieldValueAnnotationUsage2.java @@ -0,0 +1,12 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.glow.rules.test.annotation.field.value; + +import org.wildfly.glow.test.rules.classes.annotation.field.value.FieldValue; + +@FieldValue(prop = "bar bar") +public class FieldValueAnnotationUsage2 { +} diff --git a/tests/test-feature-pack/test-classes/src/main/java/org/wildfly/glow/test/rules/classes/annotation/field/value/FieldValue.java b/tests/test-feature-pack/test-classes/src/main/java/org/wildfly/glow/test/rules/classes/annotation/field/value/FieldValue.java new file mode 100644 index 00000000..b2bf0989 --- /dev/null +++ b/tests/test-feature-pack/test-classes/src/main/java/org/wildfly/glow/test/rules/classes/annotation/field/value/FieldValue.java @@ -0,0 +1,17 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.glow.test.rules.classes.annotation.field.value; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface FieldValue { + public String prop() default ""; +}