diff --git a/catalog/camel-catalog-provider-springboot/src/main/resources/org/apache/camel/springboot/catalog/others.properties b/catalog/camel-catalog-provider-springboot/src/main/resources/org/apache/camel/springboot/catalog/others.properties index 7af10075c1a5..240ffc8defee 100644 --- a/catalog/camel-catalog-provider-springboot/src/main/resources/org/apache/camel/springboot/catalog/others.properties +++ b/catalog/camel-catalog-provider-springboot/src/main/resources/org/apache/camel/springboot/catalog/others.properties @@ -29,6 +29,7 @@ shiro spring-security undertow-spring-security xml-io-dsl +xml-jaxb xml-jaxb-dsl xml-jaxp yaml-dsl diff --git a/catalog/camel-catalog-provider-springboot/src/main/resources/org/apache/camel/springboot/catalog/others/xml-jaxb.json b/catalog/camel-catalog-provider-springboot/src/main/resources/org/apache/camel/springboot/catalog/others/xml-jaxb.json new file mode 100644 index 000000000000..fb3fe1047490 --- /dev/null +++ b/catalog/camel-catalog-provider-springboot/src/main/resources/org/apache/camel/springboot/catalog/others/xml-jaxb.json @@ -0,0 +1,15 @@ +{ + "other": { + "kind": "other", + "name": "xml-jaxb", + "title": "Xml Jaxb", + "description": "Camel XML JAXB", + "deprecated": false, + "firstVersion": "3.1.0", + "label": "dsl", + "supportLevel": "Stable", + "groupId": "org.apache.camel.springboot", + "artifactId": "camel-xml-jaxb-starter", + "version": "4.0.0-SNAPSHOT" + } +} diff --git a/components-starter/camel-xml-jaxb-starter/pom.xml b/components-starter/camel-xml-jaxb-starter/pom.xml new file mode 100644 index 000000000000..be459406cdbc --- /dev/null +++ b/components-starter/camel-xml-jaxb-starter/pom.xml @@ -0,0 +1,66 @@ + + + + 4.0.0 + + org.apache.camel.springboot + components-starter + 4.0.0-SNAPSHOT + + camel-xml-jaxb-starter + jar + Spring-Boot Starter for Camel JAXB Support + + + org.springframework.boot + spring-boot-starter + ${spring-boot-version} + + + org.apache.camel + camel-xml-jaxb + ${camel-version} + + + org.graalvm.sdk + graal-sdk + ${graal-sdk-version} + provided + + + org.springframework.boot + spring-boot-starter-test + ${spring-boot-version} + test + + + org.springframework + spring-core-test + ${spring-version} + test + + + + org.apache.camel.springboot + camel-core-starter + + + + diff --git a/components-starter/camel-xml-jaxb-starter/src/main/java/org/apache/camel/xml/jaxb/springboot/JAXBRuntimeHints.java b/components-starter/camel-xml-jaxb-starter/src/main/java/org/apache/camel/xml/jaxb/springboot/JAXBRuntimeHints.java new file mode 100644 index 000000000000..d8002c20ecf9 --- /dev/null +++ b/components-starter/camel-xml-jaxb-starter/src/main/java/org/apache/camel/xml/jaxb/springboot/JAXBRuntimeHints.java @@ -0,0 +1,315 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.xml.jaxb.springboot; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.lang.annotation.Annotation; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import jakarta.xml.bind.annotation.XmlAccessOrder; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAnyAttribute; +import jakarta.xml.bind.annotation.XmlAnyElement; +import jakarta.xml.bind.annotation.XmlAttachmentRef; +import jakarta.xml.bind.annotation.XmlAttribute; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlElementDecl; +import jakarta.xml.bind.annotation.XmlElementRef; +import jakarta.xml.bind.annotation.XmlElementRefs; +import jakarta.xml.bind.annotation.XmlElementWrapper; +import jakarta.xml.bind.annotation.XmlElements; +import jakarta.xml.bind.annotation.XmlEnum; +import jakarta.xml.bind.annotation.XmlEnumValue; +import jakarta.xml.bind.annotation.XmlID; +import jakarta.xml.bind.annotation.XmlIDREF; +import jakarta.xml.bind.annotation.XmlInlineBinaryData; +import jakarta.xml.bind.annotation.XmlList; +import jakarta.xml.bind.annotation.XmlMimeType; +import jakarta.xml.bind.annotation.XmlMixed; +import jakarta.xml.bind.annotation.XmlNs; +import jakarta.xml.bind.annotation.XmlRegistry; +import jakarta.xml.bind.annotation.XmlRootElement; +import jakarta.xml.bind.annotation.XmlSchema; +import jakarta.xml.bind.annotation.XmlSchemaType; +import jakarta.xml.bind.annotation.XmlSchemaTypes; +import jakarta.xml.bind.annotation.XmlSeeAlso; +import jakarta.xml.bind.annotation.XmlTransient; +import jakarta.xml.bind.annotation.XmlType; +import jakarta.xml.bind.annotation.XmlValue; +import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapters; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.TypeReference; +import org.springframework.asm.ClassReader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.util.ReflectionUtils; + +import static org.apache.camel.spring.boot.aot.ReflectionHelper.applyIfMatch; +import static org.apache.camel.spring.boot.aot.ReflectionHelper.getClassesByAnnotations; +import static org.apache.camel.spring.boot.aot.RuntimeHintsHelper.registerClassHierarchy; + +final class JAXBRuntimeHints implements RuntimeHintsRegistrar { + + /** + * The logger. + */ + private static final Logger LOG = LoggerFactory.getLogger(JAXBRuntimeHints.class); + + private static final List> JAXB_ROOT_ANNOTATIONS = List.of( + XmlRootElement.class, XmlType.class, XmlRegistry.class, XmlJavaTypeAdapter.class, XmlSeeAlso.class); + + private static final List> JAXB_ANNOTATIONS = List.of( + XmlAccessorType.class, + XmlAnyAttribute.class, + XmlAnyElement.class, + XmlAttachmentRef.class, + XmlAttribute.class, + XmlElement.class, + XmlElementDecl.class, + XmlElementRef.class, + XmlElementRefs.class, + XmlElements.class, + XmlElementWrapper.class, + XmlEnum.class, + XmlEnumValue.class, + XmlID.class, + XmlIDREF.class, + XmlInlineBinaryData.class, + XmlList.class, + XmlMimeType.class, + XmlMixed.class, + XmlNs.class, + XmlRegistry.class, + XmlRootElement.class, + XmlSchema.class, + XmlSchemaType.class, + XmlSchemaTypes.class, + XmlSeeAlso.class, + XmlTransient.class, + XmlType.class, + XmlValue.class, + XmlJavaTypeAdapter.class, + XmlJavaTypeAdapters.class); + + private static final List NATIVE_PROXY_DEFINITIONS = List.of( + "org.glassfish.jaxb.core.marshaller.CharacterEscapeHandler", + "com.sun.xml.txw2.output.CharacterEscapeHandler", + "org.glassfish.jaxb.core.v2.schemagen.episode.Bindings", + "org.glassfish.jaxb.core.v2.schemagen.episode.SchemaBindings", + "org.glassfish.jaxb.core.v2.schemagen.episode.Klass", + "org.glassfish.jaxb.core.v2.schemagen.episode.Package", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.Annotated", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.Annotation", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.Any", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.Appinfo", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.AttrDecls", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.AttributeType", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.ComplexContent", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.ComplexExtension", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.ComplexRestriction", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.ComplexType", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.ComplexTypeHost", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.ComplexTypeModel", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.ContentModelContainer", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.Documentation", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.Element", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.ExplicitGroup", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.ExtensionType", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.FixedOrDefault", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.Import", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.List", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.LocalAttribute", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.LocalElement", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.NestedParticle", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.NoFixedFacet", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.Occurs", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.Particle", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.Redefinable", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.Schema", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.SchemaTop", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.SimpleContent", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.SimpleDerivation", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.SimpleExtension", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.SimpleRestriction", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.SimpleRestrictionModel", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.SimpleType", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.SimpleTypeHost", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.TopLevelAttribute", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.TopLevelElement", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.TypeDefParticle", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.TypeHost", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.Union", + "org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.Wildcard", + "com.sun.xml.txw2.TypedXmlWriter"); + private static final List JAXB_RUNTIME_CLASSES = List.of("org.glassfish.jaxb.runtime.v2.ContextFactory", + "com.sun.xml.internal.stream.XMLInputFactoryImpl", + "com.sun.xml.internal.stream.XMLOutputFactoryImpl", + "com.sun.org.apache.xpath.internal.functions.FuncNot", + "org.glassfish.jaxb.core.v2.model.nav.ReflectionNavigator", + "org.glassfish.jaxb.runtime.v2.runtime.property.SingleElementLeafProperty", + "org.glassfish.jaxb.runtime.v2.runtime.property.ArrayElementLeafProperty", + "org.glassfish.jaxb.runtime.v2.runtime.property.SingleElementNodeProperty", + "org.glassfish.jaxb.runtime.v2.runtime.property.SingleReferenceNodeProperty", + "org.glassfish.jaxb.runtime.v2.runtime.property.SingleMapNodeProperty", + "org.glassfish.jaxb.runtime.v2.runtime.property.ArrayElementNodeProperty", + "org.glassfish.jaxb.runtime.v2.runtime.property.ArrayReferenceNodeProperty", + "com.sun.org.apache.xerces.internal.impl.dv.xs.SchemaDVFactoryImpl", XmlAccessOrder.class.getName()); + + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + List> classes = getClassesByAnnotations(classLoader, JAXB_ROOT_ANNOTATIONS); + for (Class c : classes) { + if (c.isAnnotationPresent(XmlSeeAlso.class)) { + XmlSeeAlso annotation = c.getAnnotation(XmlSeeAlso.class); + for (Class type : annotation.value()) { + hints.reflection().registerType(type, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS); + } + } + applyIfMatch(c, XmlJavaTypeAdapter.class, XmlJavaTypeAdapter::value, + type -> hints.reflection().registerType(type, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, + MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS)); + hints.reflection().registerType(c, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, + MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS); + } + boolean classDetected = false; + for (String className : getClassesFromIndexes(classLoader)) { + registerClassHierarchy(hints, classLoader, className, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, + MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.DECLARED_FIELDS + ); + classDetected = true; + } + if (classes.isEmpty() && !classDetected) { + return; + } + // Register all JAXB indexes + hints.resources().registerPattern("*/jaxb.index"); + + hints.reflection().registerTypeIfPresent(classLoader, "jakarta.xml.bind.annotation.W3CDomHandler", + MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS); + for (Class c : JAXB_ANNOTATIONS) { + hints.reflection().registerType(c, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, + MemberCategory.INVOKE_DECLARED_METHODS); + } + hints.proxies().registerJdkProxy(TypeReference.of(XmlSeeAlso.class), TypeReference.of("org.glassfish.jaxb.core.v2.model.annotation.Locatable")); + for (String className : NATIVE_PROXY_DEFINITIONS) { + hints.proxies().registerJdkProxy(TypeReference.of(className)); + } + for (String className : JAXB_RUNTIME_CLASSES) { + hints.reflection().registerTypeIfPresent(classLoader, className, + MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS); + } + // Register the JAXB resource bundles + hints.reflection().registerTypeIfPresent(classLoader, "jakarta.xml.bind.Messages"); + hints.resources().registerPattern("jakarta/xml/bind/Messages.properties"); + hints.reflection().registerTypeIfPresent(classLoader, "jakarta.xml.bind.helpers.Messages"); + hints.resources().registerPattern("jakarta/xml/bind/helpers/Messages.properties"); + } + + private static List getClassesFromIndexes(ClassLoader classLoader) { + List classNames = new ArrayList<>(); + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(classLoader); + try { + for (Resource resource : resolver.getResources("classpath*:**/jaxb.index")) { + String filename = resource.getFilename(); + if (filename == null || filename.isBlank()) { + continue; + } + String packageName = getPackageName(resource, "jaxb.index"); + if (packageName == null) { + LOG.debug("The package name could not be found for the resource {}", resource); + continue; + } + try (BufferedReader reader = new BufferedReader(new StringReader(resource.getContentAsString(StandardCharsets.UTF_8)))) { + String line = reader.readLine(); + while (line != null) { + if (line.startsWith("#") || line.isBlank()) { + line = reader.readLine(); + continue; + } + String className = "%s%s".formatted(packageName, line.trim()); + LOG.debug("Found the class {} to register", className); + classNames.add(className); + line = reader.readLine(); + } + } + } + } catch (IOException e) { + LOG.debug("Could not load the JAXB indexes: {}", e.getMessage()); + } + return classNames; + } + + /** + * Give the package name of the given resource. + * + * @param resource the resource for which the package name is expected. + * @param fileName the name of file corresponding to the resource + * @return the package name if it could be found, {@code null} otherwise. + * @throws IOException an error occurs while trying to retrieve the package name. + */ + private static String getPackageName(Resource resource, String fileName) throws IOException { + URL url = resource.getURL(); + String protocol = url.getProtocol(); + String packageName = null; + if ("jar".equals(protocol)) { + String path = url.getPath(); + String suffix = ".jar!/"; + int index = path.indexOf(suffix); + if (index == -1) { + LOG.trace("The jar suffix could not be found in {}", path); + } else { + packageName = path.substring(index + suffix.length(), path.length() - fileName.length()); + } + } else if (resource.isFile()) { + File file = resource.getFile(); + File[] files = file.getParentFile().listFiles((dir, name) -> name.endsWith(".class")); + if (files != null && files.length > 0) { + try (InputStream is = new FileInputStream(files[0])) { + ClassReader reader = new ClassReader(is); + String className = reader.getClassName(); + int index = className.lastIndexOf('/'); + if (index == -1) { + packageName = ""; + } else { + packageName = className.substring(0, index + 1); + } + } + } else { + LOG.trace("No class file could be found in {}", file.getParentFile()); + } + } + if (packageName != null) { + packageName = packageName.replace('/', '.'); + } + return packageName; + } +} diff --git a/components-starter/camel-xml-jaxb-starter/src/main/java/org/apache/camel/xml/jaxb/springboot/graalvm/JAXBSubstitutions.java b/components-starter/camel-xml-jaxb-starter/src/main/java/org/apache/camel/xml/jaxb/springboot/graalvm/JAXBSubstitutions.java new file mode 100644 index 000000000000..b44441ff0024 --- /dev/null +++ b/components-starter/camel-xml-jaxb-starter/src/main/java/org/apache/camel/xml/jaxb/springboot/graalvm/JAXBSubstitutions.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.xml.jaxb.springboot.graalvm; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import jakarta.xml.bind.annotation.XmlSeeAlso; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; +import org.glassfish.jaxb.core.v2.model.annotation.Locatable; +import org.glassfish.jaxb.runtime.v2.model.annotation.LocatableAnnotation; +import org.glassfish.jaxb.runtime.v2.model.annotation.RuntimeInlineAnnotationReader; + +class JAXBSubstitutions { +} + +@TargetClass(RuntimeInlineAnnotationReader.class) +final class SubstituteRuntimeInlineAnnotationReader { + + @Alias + private Map,Map> packageCache; + + @Substitute + public A getFieldAnnotation(Class annotation, Field field, Locatable srcPos) { + return field.getAnnotation(annotation); + } + + @Substitute + public Annotation[] getAllFieldAnnotations(Field field, Locatable srcPos) { + return field.getAnnotations(); + } + + @Substitute + public A getClassAnnotation(Class a, Class clazz, Locatable srcPos) { + A ann = ((Class) clazz).getAnnotation(a); + return (ann != null && ann.annotationType() == XmlSeeAlso.class) ? LocatableAnnotation.create(ann, srcPos) : ann; + } + + @Substitute + public A getMethodAnnotation(Class annotation, Method method, Locatable srcPos) { + return method.getAnnotation(annotation); + } + + @Substitute + public Annotation[] getAllMethodAnnotations(Method method, Locatable srcPos) { + return method.getAnnotations(); + } + + @Substitute + public A getMethodParameterAnnotation(Class annotation, Method method, int paramIndex, + Locatable srcPos) { + Annotation[] pa = method.getParameterAnnotations()[paramIndex]; + for(Annotation a : pa) { + if (a.annotationType() == annotation) + return (A) a; + } + return null; + } + + @SuppressWarnings("unchecked") + @Substitute + public A getPackageAnnotation(Class a, Class clazz, Locatable srcPos) { + Package p = clazz.getPackage(); + if (p == null) { + return null; + } + + Map cache = packageCache.get(a); + if (cache == null) { + cache = new HashMap<>(); + packageCache.put(a, cache); + } + if (cache.containsKey(p)) { + return (A) cache.get(p); + } else { + A ann = p.getAnnotation(a); + cache.put(p, ann); + return ann; + } + } +} diff --git a/components-starter/camel-xml-jaxb-starter/src/main/resources/META-INF/LICENSE.txt b/components-starter/camel-xml-jaxb-starter/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 000000000000..6b0b1270ff0c --- /dev/null +++ b/components-starter/camel-xml-jaxb-starter/src/main/resources/META-INF/LICENSE.txt @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/components-starter/camel-xml-jaxb-starter/src/main/resources/META-INF/NOTICE.txt b/components-starter/camel-xml-jaxb-starter/src/main/resources/META-INF/NOTICE.txt new file mode 100644 index 000000000000..2e215bf2e6b1 --- /dev/null +++ b/components-starter/camel-xml-jaxb-starter/src/main/resources/META-INF/NOTICE.txt @@ -0,0 +1,11 @@ + ========================================================================= + == NOTICE file corresponding to the section 4 d of == + == the Apache License, Version 2.0, == + == in this case for the Apache Camel distribution. == + ========================================================================= + + This product includes software developed by + The Apache Software Foundation (http://www.apache.org/). + + Please read the different LICENSE files present in the licenses directory of + this distribution. diff --git a/components-starter/camel-xml-jaxb-starter/src/main/resources/META-INF/spring.provides b/components-starter/camel-xml-jaxb-starter/src/main/resources/META-INF/spring.provides new file mode 100644 index 000000000000..298e504419c9 --- /dev/null +++ b/components-starter/camel-xml-jaxb-starter/src/main/resources/META-INF/spring.provides @@ -0,0 +1,17 @@ +## --------------------------------------------------------------------------- +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## --------------------------------------------------------------------------- +provides: camel-xml-jaxb diff --git a/components-starter/camel-xml-jaxb-starter/src/main/resources/META-INF/spring/aot.factories b/components-starter/camel-xml-jaxb-starter/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 000000000000..fefd74c5e6a7 --- /dev/null +++ b/components-starter/camel-xml-jaxb-starter/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.apache.camel.xml.jaxb.springboot.JAXBRuntimeHints diff --git a/components-starter/camel-xml-jaxb-starter/src/test/java/org/apache/camel/xml/jaxb/springboot/BeanScope.java b/components-starter/camel-xml-jaxb-starter/src/test/java/org/apache/camel/xml/jaxb/springboot/BeanScope.java new file mode 100644 index 000000000000..6d1716bd8750 --- /dev/null +++ b/components-starter/camel-xml-jaxb-starter/src/test/java/org/apache/camel/xml/jaxb/springboot/BeanScope.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.xml.jaxb.springboot; + +import jakarta.xml.bind.annotation.XmlEnum; + +@XmlEnum +public enum BeanScope { + Singleton, + Request, + Prototype; + + BeanScope() { + } +} diff --git a/components-starter/camel-xml-jaxb-starter/src/test/java/org/apache/camel/xml/jaxb/springboot/Book.java b/components-starter/camel-xml-jaxb-starter/src/test/java/org/apache/camel/xml/jaxb/springboot/Book.java new file mode 100644 index 000000000000..72829dcdef57 --- /dev/null +++ b/components-starter/camel-xml-jaxb-starter/src/test/java/org/apache/camel/xml/jaxb/springboot/Book.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.xml.jaxb.springboot; + +import jakarta.xml.bind.annotation.XmlAttribute; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlRootElement; +import jakarta.xml.bind.annotation.XmlTransient; +import jakarta.xml.bind.annotation.XmlType; + +@XmlRootElement(name = "book") +@XmlType(propOrder = { "id", "name", "date" }) +public class Book extends IdentifiedType { + private String name; + private String author; + + @XmlElement(name = "title") + public void setName(String name) { + this.name = name; + } + + @XmlTransient + public void setAuthor(String author) { + this.author = author; + } + + public String getName() { + return name; + } + + public String getAuthor() { + return author; + } +} diff --git a/components-starter/camel-xml-jaxb-starter/src/test/java/org/apache/camel/xml/jaxb/springboot/IdentifiedType.java b/components-starter/camel-xml-jaxb-starter/src/test/java/org/apache/camel/xml/jaxb/springboot/IdentifiedType.java new file mode 100644 index 000000000000..82fb5facf37b --- /dev/null +++ b/components-starter/camel-xml-jaxb-starter/src/test/java/org/apache/camel/xml/jaxb/springboot/IdentifiedType.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.xml.jaxb.springboot; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; +import jakarta.xml.bind.annotation.XmlID; +import jakarta.xml.bind.annotation.XmlType; + +import org.apache.camel.spi.Metadata; + +@XmlType( + name = "identifiedType" +) +@XmlAccessorType(XmlAccessType.FIELD) +public abstract class IdentifiedType { + @XmlAttribute + @XmlID + @Metadata( + description = "The id of this node" + ) + private String id; + + public IdentifiedType() { + } + + public String getId() { + return this.id; + } + + public void setId(String value) { + this.id = value; + } +} diff --git a/components-starter/camel-xml-jaxb-starter/src/test/java/org/apache/camel/xml/jaxb/springboot/JAXBRuntimeHintsTest.java b/components-starter/camel-xml-jaxb-starter/src/test/java/org/apache/camel/xml/jaxb/springboot/JAXBRuntimeHintsTest.java new file mode 100644 index 000000000000..61aa398db013 --- /dev/null +++ b/components-starter/camel-xml-jaxb-starter/src/test/java/org/apache/camel/xml/jaxb/springboot/JAXBRuntimeHintsTest.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.xml.jaxb.springboot; + +import jakarta.xml.bind.annotation.adapters.CollapsedStringAdapter; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link JAXBRuntimeHints}. + */ +class JAXBRuntimeHintsTest { + private final RuntimeHints hints = new RuntimeHints(); + + @BeforeEach + void init() { + new JAXBRuntimeHints().registerHints(hints, getClass().getClassLoader()); + } + + @Test + void shouldRegisterHintsForJAXB() throws Exception { + assertThat(RuntimeHintsPredicates.resource().forResource("jakarta/xml/bind/Messages.properties")).accepts(hints); + assertThat(RuntimeHintsPredicates.resource().forResource("org/apache/camel/spring/boot/aot/jaxb.index")).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onType(Book.class)).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onMethod(Book.class, "getName")).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onType(IdentifiedType.class)).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onMethod(IdentifiedType.class, "setId")).accepts(hints); + assertThat(RuntimeHintsPredicates.resource().forResource("org/apache/camel/core/xml/util/jsse/jaxb.index")).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onType(BeanScope.class)).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onMethod("org.glassfish.jaxb.core.v2.model.nav.ReflectionNavigator", "getInstance")).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onType(USAddress.class)).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onType(CollapsedStringAdapter.class)).accepts(hints); + } +} diff --git a/components-starter/camel-xml-jaxb-starter/src/test/java/org/apache/camel/xml/jaxb/springboot/USAddress.java b/components-starter/camel-xml-jaxb-starter/src/test/java/org/apache/camel/xml/jaxb/springboot/USAddress.java new file mode 100644 index 000000000000..2e481659c5c9 --- /dev/null +++ b/components-starter/camel-xml-jaxb-starter/src/test/java/org/apache/camel/xml/jaxb/springboot/USAddress.java @@ -0,0 +1,201 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.xml.jaxb.springboot; + +import java.math.BigDecimal; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlType; +import jakarta.xml.bind.annotation.adapters.CollapsedStringAdapter; +import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "USAddress", propOrder = { + "name", + "street", + "city", + "state", + "zip" +}) +public class USAddress { + + @XmlElement(required = true) + protected String name; + @XmlElement(required = true) + protected String street; + @XmlElement(required = true) + protected String city; + @XmlElement(required = true) + protected String state; + @XmlElement(required = true) + protected BigDecimal zip; + @XmlAttribute + @XmlJavaTypeAdapter(CollapsedStringAdapter.class) + protected String country; + + /** + * Gets the value of the name property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getName() { + return name; + } + + /** + * Sets the value of the name property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setName(String value) { + this.name = value; + } + + /** + * Gets the value of the street property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getStreet() { + return street; + } + + /** + * Sets the value of the street property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setStreet(String value) { + this.street = value; + } + + /** + * Gets the value of the city property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getCity() { + return city; + } + + /** + * Sets the value of the city property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setCity(String value) { + this.city = value; + } + + /** + * Gets the value of the state property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getState() { + return state; + } + + /** + * Sets the value of the state property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setState(String value) { + this.state = value; + } + + /** + * Gets the value of the zip property. + * + * @return + * possible object is + * {@link java.math.BigDecimal } + * + */ + public BigDecimal getZip() { + return zip; + } + + /** + * Sets the value of the zip property. + * + * @param value + * allowed object is + * {@link java.math.BigDecimal } + * + */ + public void setZip(BigDecimal value) { + this.zip = value; + } + + /** + * Gets the value of the country property. + * + * @return + * possible object is + * {@link String } + * + */ + public String getCountry() { + if (country == null) { + return "US"; + } else { + return country; + } + } + + /** + * Sets the value of the country property. + * + * @param value + * allowed object is + * {@link String } + * + */ + public void setCountry(String value) { + this.country = value; + } + +} diff --git a/components-starter/camel-xml-jaxb-starter/src/test/resources/org/apache/camel/xml/jaxb/springboot/jaxb.index b/components-starter/camel-xml-jaxb-starter/src/test/resources/org/apache/camel/xml/jaxb/springboot/jaxb.index new file mode 100644 index 000000000000..31d2b53009d7 --- /dev/null +++ b/components-starter/camel-xml-jaxb-starter/src/test/resources/org/apache/camel/xml/jaxb/springboot/jaxb.index @@ -0,0 +1 @@ +BeanScope diff --git a/components-starter/pom.xml b/components-starter/pom.xml index 0b7845b722b7..e795cbae8be3 100644 --- a/components-starter/pom.xml +++ b/components-starter/pom.xml @@ -411,6 +411,7 @@ camel-workday-starter camel-xchange-starter camel-xj-starter + camel-xml-jaxb-starter camel-xml-jaxp-starter camel-xmlsecurity-starter camel-xmpp-starter diff --git a/core/camel-spring-boot/src/main/docs/spring-boot.adoc b/core/camel-spring-boot/src/main/docs/spring-boot.adoc index 15b40d3c424d..86a445cb1b30 100644 --- a/core/camel-spring-boot/src/main/docs/spring-boot.adoc +++ b/core/camel-spring-boot/src/main/docs/spring-boot.adoc @@ -454,3 +454,15 @@ public class MyApplicationTest { } ---- + +== Camel Spring Boot Native + +One of the most interesting features added to Spring Boot 3 is the support of GraalVM Native Image which allows you to reduce +significantly the memory footprint and the startup time of your application. Those improvements are only possible thanks +to the Ahead-Of-Time (AOT) compilation that relies on a closed-world assumption which means that everything needs to be +known at build type, all dynamic aspects included such as reflection, JNI, Proxy, and resources loading from a ClassLoader. + +For now, only Camel routes written using the Java, XML, and/or YAML DSL with basic components that don't rely on dynamic aspects to work are covered out of the box. For other components, +you will need to provide GraalVM some hints to let it know all the dynamic aspects needed by your application either by +implementing your custom https://docs.spring.io/spring-boot/docs/current/reference/html/native-image.html#native-image.advanced.custom-hints[`RuntimeHintsRegistrar`] +or by providing GraalVM JSON hint files that can be generated by the https://docs.spring.io/spring-boot/docs/current/reference/html/native-image.html#native-image.advanced.using-the-tracing-agent[Tracing Agent]. diff --git a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelAutoConfiguration.java b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelAutoConfiguration.java index b6d77a32b091..2934b2a00c05 100644 --- a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelAutoConfiguration.java +++ b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelAutoConfiguration.java @@ -23,7 +23,6 @@ import org.apache.camel.CamelContext; import org.apache.camel.ConsumerTemplate; -import org.apache.camel.ExtendedCamelContext; import org.apache.camel.FluentProducerTemplate; import org.apache.camel.ProducerTemplate; import org.apache.camel.RuntimeCamelException; @@ -39,6 +38,7 @@ import org.apache.camel.spi.PackageScanClassResolver; import org.apache.camel.spi.PackageScanResourceResolver; import org.apache.camel.spi.StartupStepRecorder; +import org.apache.camel.spring.boot.aot.CamelRuntimeHints; import org.apache.camel.spring.spi.ApplicationContextBeanRepository; import org.apache.camel.spring.spi.CamelBeanPostProcessor; import org.apache.camel.support.DefaultRegistry; @@ -56,6 +56,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Role; import org.springframework.core.OrderComparator; @@ -63,6 +64,7 @@ import org.springframework.core.env.Environment; import org.springframework.core.env.MutablePropertySources; +@ImportRuntimeHints(CamelRuntimeHints.class) @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(CamelConfigurationProperties.class) @Import(TypeConversionConfiguration.class) @@ -279,8 +281,8 @@ ConsumerTemplate consumerTemplate(CamelContext camelContext, @Bean @ConditionalOnMissingBean(PropertiesParser.class) - PropertiesParser propertiesParser() { - return new SpringPropertiesParser(); + PropertiesParser propertiesParser(Environment env) { + return new SpringPropertiesParser(env); } // We explicitly declare the destroyMethod to be "" as the Spring @Bean diff --git a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelSpringBootApplicationController.java b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelSpringBootApplicationController.java index a912b42bdabb..7f9faa134fd5 100644 --- a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelSpringBootApplicationController.java +++ b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelSpringBootApplicationController.java @@ -67,8 +67,10 @@ public void blockMainThread() { run(); } + // The method cannot to be private to prevent a failure at startup in native mode + // Refer to https://github.com/spring-projects/spring-framework/pull/30654 for more details @PreDestroy - private void destroy() { + void destroy() { main.completed(); } diff --git a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/SpringPropertiesParser.java b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/SpringPropertiesParser.java index ac3cb4d7e5b0..d05f5bd2e34a 100644 --- a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/SpringPropertiesParser.java +++ b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/SpringPropertiesParser.java @@ -18,15 +18,16 @@ import org.apache.camel.component.properties.DefaultPropertiesParser; import org.apache.camel.component.properties.PropertiesLookup; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; class SpringPropertiesParser extends DefaultPropertiesParser { // Members + private final Environment env; - @Autowired - private Environment env; + SpringPropertiesParser(Environment env) { + this.env = env; + } // Overridden diff --git a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aot/CamelRuntimeHints.java b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aot/CamelRuntimeHints.java new file mode 100644 index 000000000000..89c61475d48e --- /dev/null +++ b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aot/CamelRuntimeHints.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.spring.boot.aot; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; + +import static org.apache.camel.spring.boot.aot.RuntimeHintsHelper.registerClassHierarchy; + +/** + * {@code CamelRuntimeHints} provide the basic hints for the native compilation of a Camel application. + */ +public final class CamelRuntimeHints implements RuntimeHintsRegistrar { + + /** + * The logger. + */ + private static final Logger LOG = LoggerFactory.getLogger(CamelRuntimeHints.class); + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + // Give access to the catalog + hints.resources().registerPattern("org/apache/camel/main/*.properties"); + // Register all the camel services + registerCamelServices(hints, classLoader); + // Register collections + hints.reflection().registerType(java.util.List.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, + MemberCategory.INVOKE_PUBLIC_METHODS); + hints.reflection().registerType(java.util.Collection.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, + MemberCategory.INVOKE_PUBLIC_METHODS); + } + + /** + * Register all the Camel services that could be found in the given classloader. + * + * @param hints the hints contributed so far for the deployment unit + * @param classLoader the ClassLoader to load classpath resources with, + * or {@code null} for using the thread context class loader + * at the time of actual resource access + */ + private static void registerCamelServices(RuntimeHints hints, ClassLoader classLoader) { + hints.resources().registerPattern("META-INF/services/org/apache/camel/*"); + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(classLoader); + try { + for (Resource resource : resolver.getResources("classpath*:META-INF/services/org/apache/camel/**")) { + String filename = resource.getFilename(); + if (filename == null || filename.isBlank() || filename.endsWith(".properties")) { + continue; + } + try (BufferedReader reader = new BufferedReader(new StringReader(resource.getContentAsString(StandardCharsets.UTF_8)))) { + String line = reader.readLine(); + String prefixClass = "class="; + while (line != null) { + if (line.startsWith("#") || line.isBlank()) { + line = reader.readLine(); + continue; + } + String className = line.trim(); + if (line.startsWith(prefixClass)) { + className = line.substring(prefixClass.length()); + } + LOG.debug("Found the class {} to register", className); + registerClassHierarchy(hints, classLoader, className, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, + MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS, + MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.INTROSPECT_PUBLIC_METHODS); + line = reader.readLine(); + } + } + } + } catch (IOException e) { + LOG.debug("Could not load the Camel services: {}", e.getMessage()); + } + } +} diff --git a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aot/ReflectionHelper.java b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aot/ReflectionHelper.java new file mode 100644 index 000000000000..b6a4aadc05cf --- /dev/null +++ b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aot/ReflectionHelper.java @@ -0,0 +1,239 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.spring.boot.aot; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; +import java.lang.reflect.Constructor; +import java.lang.reflect.Parameter; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.ClassMetadata; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * {@code ReflectionHelper} utility class providing methods needed for the native mode. + */ +public final class ReflectionHelper { + + /** + * The logger. + */ + private static final Logger LOG = LoggerFactory.getLogger(ReflectionHelper.class); + + private ReflectionHelper() { + + } + + /** + * Apply a specific action anytime the annotation is found in the class according to the type of target supported + * by the annotation which can be either {@link ElementType#TYPE}, {@link ElementType#CONSTRUCTOR}, + * {@link ElementType#METHOD}, {@link ElementType#FIELD}, or {@link ElementType#PARAMETER}. + * + * @param c the target class + * @param a the target type of annotation + * @param getter the method allowing to extract the excepted values from the annotation + * @param onMatch the action to perform in case of a match + * @param the type of the target annotation + * @param the type of the content extracted from the annotation found + */ + public static void applyIfMatch(Class c, Class a, Function getter, + Consumer onMatch) { + Set targets = null; + if (a.isAnnotationPresent(Target.class)) { + targets = Collections.newSetFromMap(new EnumMap<>(ElementType.class)); + targets.addAll(Arrays.asList(a.getAnnotation(Target.class).value())); + } + if ((targets == null || targets.contains(ElementType.TYPE)) && c.isAnnotationPresent(a)) { + onMatch.accept(getter.apply(c.getAnnotation(a))); + } + boolean checkConstructors = targets == null || targets.contains(ElementType.CONSTRUCTOR); + boolean checkParameters = targets == null || targets.contains(ElementType.PARAMETER); + if (checkConstructors || checkParameters) { + for (Constructor constructor : c.getDeclaredConstructors()) { + if (checkConstructors && constructor.isAnnotationPresent(a)) { + onMatch.accept(getter.apply(constructor.getAnnotation(a))); + } + if (checkParameters) { + for (Parameter parameter : constructor.getParameters()) { + if (parameter.isAnnotationPresent(a)) { + onMatch.accept(getter.apply(parameter.getAnnotation(a))); + } + } + } + } + } + if (targets == null || targets.contains(ElementType.FIELD)) { + ReflectionUtils.doWithFields(c, + field -> onMatch.accept(getter.apply(field.getAnnotation(a))), field -> field.isAnnotationPresent(a)); + } + boolean checkMethods = targets == null || targets.contains(ElementType.METHOD); + if (checkMethods || checkParameters) { + ReflectionUtils.doWithMethods( + c, + method -> { + if (checkMethods && method.isAnnotationPresent(a)) { + onMatch.accept(getter.apply(method.getAnnotation(a))); + } + if (checkParameters) { + for (Parameter parameter : method.getParameters()) { + if (parameter.isAnnotationPresent(a)) { + onMatch.accept(getter.apply(parameter.getAnnotation(a))); + } + } + } + } + ); + } + } + + /** + * Give all the classes available in the given class loader that are annotated with at least one of the annotations. + * + * @param classLoader the class loader from which the classes to find are loaded + * @param annotations the target annotations + * @return the list of classes that are annotated with at least one of the annotations. + */ + public static List> getClassesByAnnotations(ClassLoader classLoader, List> annotations) { + return getClassesByFilters(classLoader, annotations.stream().map(AnnotationTypeFilter::new).collect(Collectors.toList())); + } + + /** + * Give all the classes available in the given class loader that match with at least one of the filters. + * + * @param classLoader the class loader from which the classes to find are loaded + * @param includeFilters the filters to apply the classes found + * @return a list of classes that match with at least one of the given filters + */ + public static List> getClassesByFilters(ClassLoader classLoader, List includeFilters) { + ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false) { + @Override + protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { + return true; + } + }; + provider.setResourceLoader(new PathMatchingResourcePatternResolver(classLoader)); + provider.setMetadataReaderFactory(new SafeMetadataReaderFactory(provider.getMetadataReaderFactory())); + provider.addExcludeFilter( + (metadata, factory) -> { + String className = metadata.getClassMetadata().getClassName(); + return className.startsWith("org.springframework.") || className.startsWith("java.") || className.startsWith("jakarta."); + }); + for (TypeFilter filter : includeFilters) { + provider.addIncludeFilter(filter); + } + return provider.findCandidateComponents("") + .stream() + .map(b -> asClass(b, classLoader)) + .collect(Collectors.toList()); + } + + /** + * Convert the given bean definition into a class. + * @param bean the bean definition to convert. + * @param classLoader the classloader from which the class of the bean is loaded + * @return the class corresponding to the bean definition if it could be found, {@code null} otherwise. + */ + private static Class asClass(BeanDefinition bean, ClassLoader classLoader) { + String beanClassName = bean.getBeanClassName(); + if (beanClassName == null) { + LOG.debug("The name of the class corresponding to the bean '{}' could not be found", bean); + } else { + try { + return ClassUtils.forName(beanClassName, classLoader); + } catch (ClassNotFoundException | NoClassDefFoundError e) { + LOG.debug("The class corresponding to the bean '{}' could not be found: {}", bean, e.getMessage()); + } + } + return null; + } + + /** + * {@code SafeMetadataReaderFactory} is a specific {@link MetadataReaderFactory} whose methods never throw any + * exceptions, if an error occurs while calling the underlying {@link MetadataReaderFactory} a debug message is + * logged and the default result is returned. + */ + private static class SafeMetadataReaderFactory implements MetadataReaderFactory { + + /** + * The instance of the default result in case of an error. + */ + private static final MetadataReader DEFAULT = new MetadataReader() { + @Override + public Resource getResource() { + return new ByteArrayResource(new byte[0]); + } + + @Override + public ClassMetadata getClassMetadata() { + return AnnotationMetadata.introspect(Object.class); + } + + @Override + public AnnotationMetadata getAnnotationMetadata() { + return AnnotationMetadata.introspect(Object.class); + } + }; + private final MetadataReaderFactory delegate; + + SafeMetadataReaderFactory(MetadataReaderFactory delegate) { + this.delegate = delegate; + } + + @Override + public MetadataReader getMetadataReader(String className) { + try { + return delegate.getMetadataReader(className); + } catch (Exception | NoClassDefFoundError e) { + LOG.debug("Could not get the metadata of the class {}", className); + } + return DEFAULT; + } + + @Override + public MetadataReader getMetadataReader(Resource resource) { + try { + return delegate.getMetadataReader(resource); + } catch (Exception | NoClassDefFoundError e) { + LOG.debug("Could not get the metadata of the resource {}", resource); + } + return DEFAULT; + } + } +} diff --git a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aot/RuntimeHintsHelper.java b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aot/RuntimeHintsHelper.java new file mode 100644 index 000000000000..d759cc8b3f47 --- /dev/null +++ b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/aot/RuntimeHintsHelper.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.spring.boot.aot; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.util.ClassUtils; + +public final class RuntimeHintsHelper { + + /** + * The logger. + */ + private static final Logger LOG = LoggerFactory.getLogger(RuntimeHintsHelper.class); + + private RuntimeHintsHelper() { + } + + + /** + * Register the given class and all its parent classes by applying the given member categories. + * + * @param hints the hints contributed so far for the deployment unit + * @param classLoader the ClassLoader to load classpath resources with, + * or {@code null} for using the thread context class loader + * at the time of actual resource access + * @param className the name of the class to register + * @param memberCategories the member categories to apply + */ + public static void registerClassHierarchy(RuntimeHints hints, ClassLoader classLoader, String className, + MemberCategory... memberCategories) { + try { + registerClassHierarchy(hints, ClassUtils.forName(className, classLoader), memberCategories); + } catch (ClassNotFoundException | NoClassDefFoundError e) { + LOG.debug("The class {} cannot be found", className); + } + } + + /** + * Register the given class and all its parent classes by applying the given member categories. + * + * @param hints the hints contributed so far for the deployment unit + * @param clazz the class to register + * @param memberCategories the member categories to apply + */ + public static void registerClassHierarchy(RuntimeHints hints, Class clazz, MemberCategory... memberCategories) { + if (clazz.isInterface() || clazz.isArray()) { + return; + } + while (clazz != Object.class) { + hints.reflection().registerType(clazz, memberCategories); + clazz = clazz.getSuperclass(); + } + } +} diff --git a/core/camel-spring-boot/src/main/resources/META-INF/native-image/org.apache.camel.springboot/camel-spring-boot/native-image.properties b/core/camel-spring-boot/src/main/resources/META-INF/native-image/org.apache.camel.springboot/camel-spring-boot/native-image.properties new file mode 100644 index 000000000000..6e7d06f2fe9e --- /dev/null +++ b/core/camel-spring-boot/src/main/resources/META-INF/native-image/org.apache.camel.springboot/camel-spring-boot/native-image.properties @@ -0,0 +1 @@ +Args = -H:+AddAllCharsets diff --git a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aot/CamelRuntimeHintsTest.java b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aot/CamelRuntimeHintsTest.java new file mode 100644 index 000000000000..d942ac47c301 --- /dev/null +++ b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aot/CamelRuntimeHintsTest.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.spring.boot.aot; + +import org.apache.camel.CamelContext; +import org.apache.camel.language.simple.SimpleLanguage; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link CamelRuntimeHints}. + */ +class CamelRuntimeHintsTest { + + private final RuntimeHints hints = new RuntimeHints(); + + @BeforeEach + void init() { + new CamelRuntimeHints().registerHints(hints, getClass().getClassLoader()); + } + + @Test + void shouldRegisterHintsForCamelServices() throws Exception { + assertThat(RuntimeHintsPredicates.resource().forResource("META-INF/services/org/apache/camel/language/simple")).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onConstructor(SimpleLanguage.class.getConstructor())).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onMethod(SimpleLanguage.class.getMethod("init"))).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onMethod(SimpleLanguage.class.getMethod("setCamelContext", CamelContext.class))).accepts(hints); + } + + @Test + void shouldRegisterHintsForCamelCatalog() { + assertThat(RuntimeHintsPredicates.resource().forResource("org/apache/camel/main/components.properties")).accepts(hints); + } +} diff --git a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aot/ReflectionHelperTest.java b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aot/ReflectionHelperTest.java new file mode 100644 index 000000000000..c01e26e115fa --- /dev/null +++ b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/aot/ReflectionHelperTest.java @@ -0,0 +1,142 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.spring.boot.aot; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link ReflectionHelperTest}. + */ +class ReflectionHelperTest { + + private final AtomicInteger counter = new AtomicInteger(); + + @Test + void shouldAlwaysApply() { + ReflectionHelper.applyIfMatch(Foo.class, All.class, a -> null, x -> counter.incrementAndGet()); + assertThat(counter.get()).isEqualTo(11); + } + + @Test + void shouldNeverApply() { + ReflectionHelper.applyIfMatch(Foo.class, None.class, a -> null, x -> counter.incrementAndGet()); + assertThat(counter.get()).isZero(); + } + + @Test + void shouldApplyToClassOnly() { + ReflectionHelper.applyIfMatch(Foo.class, OnlyType.class, a -> null, x -> counter.incrementAndGet()); + assertThat(counter.get()).isEqualTo(1); + } + + @Test + void shouldApplyToOneSpecificConstructor() { + ReflectionHelper.applyIfMatch(Foo.class, OnlyConstructor.class, a -> null, x -> counter.incrementAndGet()); + assertThat(counter.get()).isEqualTo(1); + } + + @Test + void shouldApplyToOneSpecificField() { + ReflectionHelper.applyIfMatch(Foo.class, OnlyField.class, a -> null, x -> counter.incrementAndGet()); + assertThat(counter.get()).isEqualTo(1); + } + + @Test + void shouldApplyToOneSpecificMethod() { + ReflectionHelper.applyIfMatch(Foo.class, OnlyMethod.class, a -> null, x -> counter.incrementAndGet()); + assertThat(counter.get()).isEqualTo(1); + } + + @Test + void shouldApplyToSpecificParameters() { + ReflectionHelper.applyIfMatch(Foo.class, OnlyParameter.class, a -> null, x -> counter.incrementAndGet()); + assertThat(counter.get()).isEqualTo(2); + } + + @OnlyType + @All + public static class Foo { + + @All + private String someField1; + @OnlyField + @All + private String someField2; + + @All + private Foo() {} + + @OnlyConstructor + @All + private Foo(@OnlyParameter @All String someParam1, @All String someParam2) {} + + @All + private void someMethod1(@All String someParam1, @All @OnlyParameter String someParam2) {} + + @OnlyMethod + @All + private void someMethod2() {} + } + + @Retention(RetentionPolicy.RUNTIME) + public @interface All { + + } + + @Retention(RetentionPolicy.RUNTIME) + public @interface None { + + } + + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + public @interface OnlyType { + + } + + @Target(ElementType.FIELD) + @Retention(RetentionPolicy.RUNTIME) + public @interface OnlyField { + + } + + @Target(ElementType.CONSTRUCTOR) + @Retention(RetentionPolicy.RUNTIME) + public @interface OnlyConstructor { + + } + + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface OnlyMethod { + + } + + @Target(ElementType.PARAMETER) + @Retention(RetentionPolicy.RUNTIME) + public @interface OnlyParameter { + + } +} diff --git a/dsl-starter/camel-xml-jaxb-dsl-starter/pom.xml b/dsl-starter/camel-xml-jaxb-dsl-starter/pom.xml index aec33256ef64..e7527e5b0d7d 100644 --- a/dsl-starter/camel-xml-jaxb-dsl-starter/pom.xml +++ b/dsl-starter/camel-xml-jaxb-dsl-starter/pom.xml @@ -49,5 +49,10 @@ camel-xml-jaxb-dsl ${camel-version} + + org.apache.camel.springboot + camel-xml-jaxb-starter + ${camel-version} + \ No newline at end of file diff --git a/dsl-starter/camel-xml-jaxb-dsl-starter/src/main/java/org/apache/camel/dsl/xml/jaxb/springboot/aot/XMLDSLRuntimeHints.java b/dsl-starter/camel-xml-jaxb-dsl-starter/src/main/java/org/apache/camel/dsl/xml/jaxb/springboot/aot/XMLDSLRuntimeHints.java new file mode 100644 index 000000000000..75abf68a13c1 --- /dev/null +++ b/dsl-starter/camel-xml-jaxb-dsl-starter/src/main/java/org/apache/camel/dsl/xml/jaxb/springboot/aot/XMLDSLRuntimeHints.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.dsl.xml.jaxb.springboot.aot; + +import java.util.ArrayList; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; + +final class XMLDSLRuntimeHints implements RuntimeHintsRegistrar { + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints.reflection().registerType(ArrayList.class, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); + } +} diff --git a/dsl-starter/camel-xml-jaxb-dsl-starter/src/main/resources/META-INF/spring/aot.factories b/dsl-starter/camel-xml-jaxb-dsl-starter/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 000000000000..0c240e4c506a --- /dev/null +++ b/dsl-starter/camel-xml-jaxb-dsl-starter/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.apache.camel.dsl.xml.jaxb.springboot.aot.XMLDSLRuntimeHints diff --git a/pom.xml b/pom.xml index a27fc89cccdf..a2a463af2002 100644 --- a/pom.xml +++ b/pom.xml @@ -119,6 +119,7 @@ 1.7.0.Alpha10 1.11.0 4.0.12 + 22.3.2 4.0.0 2.3.0 3.11.0