From e6bcbe6edf14e592900ca574074b82c2774a51f9 Mon Sep 17 00:00:00 2001 From: Joram Barrez Date: Tue, 5 Dec 2023 15:22:56 +0100 Subject: [PATCH 01/10] Initial spring boot aot/native support Co-authored-by: Josh Long --- ...BeanFactoryInitializationAotProcessor.java | 250 +++++++++++++++++ ...BeanFactoryInitializationAotProcessor.java | 38 +++ .../spring/aot/FlowableSpringAotUtils.java | 85 ++++++ ...BeanFactoryInitializationAotProcessor.java | 169 +++++++++++ ...BeanFactoryInitializationAotProcessor.java | 262 ++++++++++++++++++ .../spring/aot/MybatisMapperTypeUtils.java | 63 +++++ ...BeanFactoryInitializationAotProcessor.java | 133 +++++++++ .../resources/META-INF/spring/aot.factories | 5 + 8 files changed, 1005 insertions(+) create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableBeanFactoryInitializationAotProcessor.java create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableMybatisMappersBeanFactoryInitializationAotProcessor.java create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableSpringAotUtils.java create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisApplicationSpecificBeanFactoryInitializationAotProcessor.java create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisGlobalBeanFactoryInitializationAotProcessor.java create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisMapperTypeUtils.java create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisMappersBeanFactoryInitializationAotProcessor.java create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/spring/aot.factories diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableBeanFactoryInitializationAotProcessor.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableBeanFactoryInitializationAotProcessor.java new file mode 100644 index 00000000000..5f67935d7ab --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableBeanFactoryInitializationAotProcessor.java @@ -0,0 +1,250 @@ +/* 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. + */ +package org.flowable.spring.aot; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.ibatis.javassist.util.proxy.ProxyFactory; +import org.apache.ibatis.scripting.defaults.RawLanguageDriver; +import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver; +import org.apache.ibatis.type.TypeHandler; +import org.flowable.bpmn.converter.BpmnXMLConverter; +import org.flowable.bpmn.model.ImplementationType; +import org.flowable.bpmn.model.ServiceTask; +import org.flowable.common.engine.api.query.Query; +import org.flowable.common.engine.impl.db.ListQueryParameterObject; +import org.flowable.common.engine.impl.de.odysseus.el.ExpressionFactoryImpl; +import org.flowable.common.engine.impl.persistence.cache.EntityCacheImpl; +import org.flowable.common.engine.impl.persistence.entity.ByteArrayRef; +import org.flowable.common.engine.impl.persistence.entity.Entity; +import org.flowable.common.engine.impl.persistence.entity.EntityManager; +import org.flowable.common.engine.impl.persistence.entity.TablePageQueryImpl; +import org.flowable.eventregistry.impl.db.SetChannelDefinitionTypeAndImplementationCustomChange; +import org.flowable.variable.api.types.VariableType; +import org.flowable.variable.service.impl.InternalVariableInstanceQueryImpl; +import org.flowable.variable.service.impl.QueryVariableValue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.TypeReference; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.autoconfigure.AutoConfigurationPackages; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * @author Josh Long + * @author Joram Barrez + */ +class FlowableBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor { + + private final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + + private final Logger log = LoggerFactory.getLogger(getClass()); + + FlowableBeanFactoryInitializationAotProcessor() { + } + + + private Set processResources() { + return resources("processes/**/*.bpmn20.xml"); + } + + private Set flowablePersistenceResources() throws Exception { + + var resources = new HashSet(); + resources.addAll(resources("org/flowable/**/*.sql", "org/flowable/**/*.xml", "org/flowable/**/*.txt", "org/flowable/**/*.xsd", "org/flowable/**/*.properties")); + resources.addAll(processResources()); + + for (var e : "xml,yaml,yml".split(",")) + resources.add(new ClassPathResource("flowable-default." + e)); + + resources.addAll(from(this.resolver.getResources("META-INF/services/org.flowable.common.engine.impl.EngineConfigurator"))); + resources.addAll(from(this.resolver.getResources("org/flowable/common/engine/impl/de/odysseus/el/misc/LocalStrings"))); + return resources.stream() + .filter(Resource::exists) + .collect(Collectors.toSet()); + } + + @Override + public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { + return (generationContext, beanFactoryInitializationCode) -> { + var hints = generationContext.getRuntimeHints(); + beanFactory.getBeanClassLoader(); + try { + + var memberCategories = MemberCategory.values(); + + + for (var c : Set.of(ProxyFactory.class, XMLLanguageDriver.class, + org.apache.ibatis.logging.slf4j.Slf4jImpl.class, EntityCacheImpl.class, + RawLanguageDriver.class, org.apache.ibatis.session.Configuration.class, HashSet.class)) + hints.reflection().registerType(c, memberCategories); + + var types = new Class[]{ + TypeHandler.class, + EntityManager.class, + Entity.class, + Query.class, + VariableType.class, + ListQueryParameterObject.class, + TablePageQueryImpl.class, + SetChannelDefinitionTypeAndImplementationCustomChange.class, + ByteArrayRef.class, + InternalVariableInstanceQueryImpl.class, + QueryVariableValue.class, + ExpressionFactoryImpl.class + }; + + var packagesSet = new HashSet(); + packagesSet.add("org.apache.ibatis"); + packagesSet.add("org.flowable"); + packagesSet.addAll(AutoConfigurationPackages.get(beanFactory)); + var packages = packagesSet.toArray(new String[0]); + + for (var t : types) { + hints.reflection().registerType(t, memberCategories); + var subTypes = FlowableSpringAotUtils.getSubTypesOf(t, packages); + for (var s : subTypes) { + if (StringUtils.hasText(s)) { + hints.reflection().registerType(TypeReference.of(s), memberCategories); + } + } + } + + var resources = new HashSet(); + resources.addAll(flowablePersistenceResources()); + resources.addAll(""" + flowable-default.properties + flowable-default.xml + flowable-default.yaml + flowable-default.yml + """ + .stripIndent() + .stripLeading() + .trim() + .lines() + .map(l -> l.strip().trim()) + .filter(l -> !l.isEmpty()) + .map(ClassPathResource::new) + .toList()); + + + for (var resource : resources) { + if (resource.exists()) { + hints.resources().registerResource(resource); + } + } + + + // here lay dragons; we're going to attempt to proactively register aot hints for beans referenced within a process definition + var processDefinitions = this.processResources(); + for (var processDefinitionXmlResource : processDefinitions) { + Assert.state(processDefinitionXmlResource.exists(), "the process definition file [" + processDefinitionXmlResource.getFilename() + + "] does not exist"); + + hints.resources().registerResource(processDefinitionXmlResource); + try (var in = processDefinitionXmlResource.getInputStream()) { + + var bpmnXMLConverter = new BpmnXMLConverter(); + var bpmnModel = bpmnXMLConverter.convertToBpmnModel(() -> in, false, false); + var serviceTasks = bpmnModel.getMainProcess().findFlowElementsOfType(ServiceTask.class); + for (var st : serviceTasks) { + if (st.getImplementationType().equals(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION)) { + var expression = st.getImplementation(); + var expressionWithoutDelimiters = expression.substring(2); + expressionWithoutDelimiters = expressionWithoutDelimiters.substring(0, expressionWithoutDelimiters.length() - 1); + var beanName = expressionWithoutDelimiters; + try { + var beanDefinition = beanFactory.getBeanDefinition(beanName); + hints.reflection().registerType(TypeReference.of(beanDefinition.getBeanClassName()), MemberCategory.values()); + + log.debug("registering hint for bean name [" + beanName + "]"); + }// + catch (Throwable throwable) { + log.error("couldn't find bean named [" + beanName + "]"); + } + + } + } + } + } + + + }// + catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + }; + } + + + private static Set from(T[] t) { + return new HashSet<>(Arrays.asList(t)); + } + + private static Resource newResourceFor(Resource in) { + try { + var marker = "jar!"; + var externalFormOfUrl = in.getURL().toExternalForm(); + if (externalFormOfUrl.contains(marker)) { + var rest = externalFormOfUrl.substring(externalFormOfUrl.lastIndexOf(marker) + marker.length()); + return new ClassPathResource(rest); + }// + else { + // ugh i think this only works for maven? what about gradle? + var classesSubstring = "classes/"; + var locationOfClassesInUrl = externalFormOfUrl.indexOf(classesSubstring); + if (locationOfClassesInUrl != -1) { + return new ClassPathResource(externalFormOfUrl.substring(locationOfClassesInUrl + classesSubstring.length())); + } + + } + + return in; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + Set resources(String... patterns) { + return Stream + .of(patterns) + .map(path -> ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + path) + .flatMap(p -> { + try { + return Stream.of(this.resolver.getResources(p)); + }// + catch (IOException e) { + throw new RuntimeException(e); + } + }) + .map(FlowableBeanFactoryInitializationAotProcessor::newResourceFor) + .filter(Resource::exists) + .collect(Collectors.toSet()); + } + + +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableMybatisMappersBeanFactoryInitializationAotProcessor.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableMybatisMappersBeanFactoryInitializationAotProcessor.java new file mode 100644 index 00000000000..668f4e8fac4 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableMybatisMappersBeanFactoryInitializationAotProcessor.java @@ -0,0 +1,38 @@ +/* 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. + */ +package org.flowable.spring.aot; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.BeanFactory; + +/** + * @author Josh Long + */ +class FlowableMybatisMappersBeanFactoryInitializationAotProcessor + extends MybatisMappersBeanFactoryInitializationAotProcessor { + + FlowableMybatisMappersBeanFactoryInitializationAotProcessor() { + + } + + @Override + protected List getPackagesToScan(BeanFactory b) { + var defaults = super.getPackagesToScan(b); + var l = new ArrayList(); + l.add("org.flowable"); + l.addAll(defaults); + return l; + } +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableSpringAotUtils.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableSpringAotUtils.java new file mode 100644 index 00000000000..3c172cc771d --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableSpringAotUtils.java @@ -0,0 +1,85 @@ +/* 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. + */ +package org.flowable.spring.aot; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.type.filter.AssignableTypeFilter; + +/** + * @author Josh Long + */ +final class FlowableSpringAotUtils { + + private static final Logger log = LoggerFactory.getLogger(FlowableSpringAotUtils.class); + + private FlowableSpringAotUtils() { + } + + static Resource newResourceFor(Resource in) { + try { + var marker = "jar!"; + var p = in.getURL().toExternalForm(); + var rest = p.substring(p.lastIndexOf(marker) + marker.length()); + return new ClassPathResource(rest); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + static boolean isSerializable(Class clazz) { + return Serializable.class.isAssignableFrom(clazz); + } + + static void debug(String message, Collection tCollection) { + log.debug(message + System.lineSeparator()); + for (var t : tCollection) + log.debug('\t' + t.toString()); + log.debug(System.lineSeparator()); + } + + static String packageToPath(String packageName) { + var sb = new StringBuilder(); + for (var c : packageName.toCharArray()) + sb.append(c == '.' ? '/' : c); + return sb.toString(); + } + + + static Set getSubTypesOf(Class clzzName, String... packages) { + var set = new HashSet(); + + for (var p : packages) { + var classPathScanningCandidateComponentProvider = new ClassPathScanningCandidateComponentProvider(false); + + classPathScanningCandidateComponentProvider.addIncludeFilter(new AssignableTypeFilter(clzzName)); + + var results = classPathScanningCandidateComponentProvider.findCandidateComponents(p); + for (var r : results) + set.add(r.getBeanClassName()); + } + + return set; + + } +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisApplicationSpecificBeanFactoryInitializationAotProcessor.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisApplicationSpecificBeanFactoryInitializationAotProcessor.java new file mode 100644 index 00000000000..06748d48147 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisApplicationSpecificBeanFactoryInitializationAotProcessor.java @@ -0,0 +1,169 @@ +/* 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. + */ +package org.flowable.spring.aot; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Function; + +import org.apache.ibatis.annotations.DeleteProvider; +import org.apache.ibatis.annotations.InsertProvider; +import org.apache.ibatis.annotations.SelectProvider; +import org.apache.ibatis.annotations.UpdateProvider; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.TypeReference; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.autoconfigure.AutoConfigurationPackages; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * Register hints based upon the structure of a particular user's Spring Boot application + * packages and {@link org.springframework.beans.factory.BeanFactory} + * + * @author Josh Long + */ +class MybatisApplicationSpecificBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor { + + private final PathMatchingResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); + + MybatisApplicationSpecificBeanFactoryInitializationAotProcessor() { + } + + private Collection attemptToRegisterXmlResourcesForBasePackage( + ConfigurableListableBeanFactory beanFactory) throws Exception { + var set = new HashSet(); + for (var packageName : AutoConfigurationPackages.get(beanFactory)) { + Assert.hasText(packageName, "the package name must not be empty!"); + var path = FlowableSpringAotUtils.packageToPath(packageName); + for (var resolvedXmlResource : this.resourcePatternResolver + .getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + path + "/**/*.xml")) { + var fqn = resolvedXmlResource.getURI().toString(); + if (resolvedXmlResource.exists()) { + var np = fqn.substring(fqn.indexOf(path)); + var npr = new ClassPathResource(np); + set.add(npr); + } + } + } + return set; + } + + @Override + public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { + + if (!ClassUtils.isPresent("org.mybatis.spring.mapper.MapperFactoryBean", beanFactory.getBeanClassLoader())) + return null; + + try { + var classesToRegister = new HashSet>(); + var proxiesToRegister = new HashSet>(); + var resourcesToRegister = new HashSet(); + resourcesToRegister.addAll(attemptToRegisterXmlResourcesForBasePackage(beanFactory)); + + // Flowable doesn't use mapper classes +// var beanNames = beanFactory.getBeanNamesForType(MapperFactoryBean.class); +// for (var beanName : beanNames) { +// var beanDefinition = beanFactory.getBeanDefinition(beanName.substring(1)); +// var mapperInterface = beanDefinition.getPropertyValues().getPropertyValue("mapperInterface"); +// if (mapperInterface != null && mapperInterface.getValue() != null) { +// var mapperInterfaceType = (Class) mapperInterface.getValue(); +// if (mapperInterfaceType != null) { +// proxiesToRegister.add(mapperInterfaceType); +// resourcesToRegister +// .add(new ClassPathResource(mapperInterfaceType.getName().replace('.', '/').concat(".xml"))); +// registerReflectionTypeIfNecessary(mapperInterfaceType, classesToRegister); +// registerMapperRelationships(mapperInterfaceType, classesToRegister); +// } +// } +// } + + return (generationContext, beanFactoryInitializationCode) -> { + var mcs = MemberCategory.values(); + var runtimeHints = generationContext.getRuntimeHints(); + FlowableSpringAotUtils.debug("proxies", proxiesToRegister); + FlowableSpringAotUtils.debug("classes for reflection", classesToRegister); + FlowableSpringAotUtils.debug("resources", resourcesToRegister); + for (var c : proxiesToRegister) { + runtimeHints.proxies().registerJdkProxy(c); + runtimeHints.reflection().registerType(c, mcs); + } + for (var c : classesToRegister) { + runtimeHints.reflection().registerType(c, mcs); + if (FlowableSpringAotUtils.isSerializable(c)) + runtimeHints.serialization().registerType(TypeReference.of(c.getName())); + } + for (var r : resourcesToRegister) { + if (r.exists()) { + runtimeHints.resources().registerResource(r); + } + } + }; + } // + catch (Exception e) { + throw new RuntimeException(e); + } + } + + @SafeVarargs + private void registerSqlProviderTypes(Method method, Set> registry, + Class annotationType, Function>... providerTypeResolvers) { + for (T annotation : method.getAnnotationsByType(annotationType)) { + for (Function> providerTypeResolver : providerTypeResolvers) { + registerReflectionTypeIfNecessary(providerTypeResolver.apply(annotation), registry); + } + } + } + + private void registerReflectionTypeIfNecessary(Class type, Set> registry) { + if (!type.isPrimitive() && !type.getName().startsWith("java")) { + registry.add(type); + } + } + + private void registerMapperRelationships(Class mapperInterfaceType, Set> registry) { + var methods = ReflectionUtils.getAllDeclaredMethods(mapperInterfaceType); + for (var method : methods) { + if (method.getDeclaringClass() != Object.class) { + + ReflectionUtils.makeAccessible(method); + + registerSqlProviderTypes(method, registry, SelectProvider.class, SelectProvider::value, + SelectProvider::type); + registerSqlProviderTypes(method, registry, InsertProvider.class, InsertProvider::value, + InsertProvider::type); + registerSqlProviderTypes(method, registry, UpdateProvider.class, UpdateProvider::value, + UpdateProvider::type); + registerSqlProviderTypes(method, registry, DeleteProvider.class, DeleteProvider::value, + DeleteProvider::type); + + var returnType = MybatisMapperTypeUtils.resolveReturnClass(mapperInterfaceType, method); + registerReflectionTypeIfNecessary(returnType, registry); + + MybatisMapperTypeUtils.resolveParameterClasses(mapperInterfaceType, method) + .forEach(x -> registerReflectionTypeIfNecessary(x, registry)); + } + } + } + +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisGlobalBeanFactoryInitializationAotProcessor.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisGlobalBeanFactoryInitializationAotProcessor.java new file mode 100644 index 00000000000..af8d9ea4a2f --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisGlobalBeanFactoryInitializationAotProcessor.java @@ -0,0 +1,262 @@ +/* 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. + */ +package org.flowable.spring.aot; + +import java.io.IOException; +import java.io.Serializable; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.RandomAccess; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.stream.Stream; + +import org.apache.ibatis.annotations.CacheNamespace; +import org.apache.ibatis.annotations.Delete; +import org.apache.ibatis.annotations.DeleteProvider; +import org.apache.ibatis.annotations.Flush; +import org.apache.ibatis.annotations.Insert; +import org.apache.ibatis.annotations.InsertProvider; +import org.apache.ibatis.annotations.Many; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.One; +import org.apache.ibatis.annotations.Options; +import org.apache.ibatis.annotations.Property; +import org.apache.ibatis.annotations.Result; +import org.apache.ibatis.annotations.Results; +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.SelectProvider; +import org.apache.ibatis.annotations.Update; +import org.apache.ibatis.annotations.UpdateProvider; +import org.apache.ibatis.builder.CacheRefResolver; +import org.apache.ibatis.builder.ResultMapResolver; +import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder; +import org.apache.ibatis.builder.annotation.MethodResolver; +import org.apache.ibatis.builder.annotation.ProviderContext; +import org.apache.ibatis.builder.annotation.ProviderMethodResolver; +import org.apache.ibatis.cache.Cache; +import org.apache.ibatis.cache.CacheKey; +import org.apache.ibatis.cache.NullCacheKey; +import org.apache.ibatis.cache.decorators.BlockingCache; +import org.apache.ibatis.cache.decorators.FifoCache; +import org.apache.ibatis.cache.decorators.LoggingCache; +import org.apache.ibatis.cache.decorators.LruCache; +import org.apache.ibatis.cache.decorators.SerializedCache; +import org.apache.ibatis.cache.decorators.SynchronizedCache; +import org.apache.ibatis.cache.decorators.TransactionalCache; +import org.apache.ibatis.cache.decorators.WeakCache; +import org.apache.ibatis.cache.impl.PerpetualCache; +import org.apache.ibatis.cursor.Cursor; +import org.apache.ibatis.javassist.util.proxy.ProxyFactory; +import org.apache.ibatis.javassist.util.proxy.RuntimeSupport; +import org.apache.ibatis.logging.Log; +import org.apache.ibatis.logging.LogFactory; +import org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl; +import org.apache.ibatis.logging.jdbc.BaseJdbcLogger; +import org.apache.ibatis.logging.jdbc.ConnectionLogger; +import org.apache.ibatis.logging.jdbc.PreparedStatementLogger; +import org.apache.ibatis.logging.jdbc.ResultSetLogger; +import org.apache.ibatis.logging.jdbc.StatementLogger; +import org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl; +import org.apache.ibatis.logging.log4j.Log4jImpl; +import org.apache.ibatis.logging.log4j2.Log4j2AbstractLoggerImpl; +import org.apache.ibatis.logging.log4j2.Log4j2Impl; +import org.apache.ibatis.logging.log4j2.Log4j2LoggerImpl; +import org.apache.ibatis.logging.nologging.NoLoggingImpl; +import org.apache.ibatis.logging.slf4j.Slf4jImpl; +import org.apache.ibatis.logging.stdout.StdOutImpl; +import org.apache.ibatis.mapping.ResultFlag; +import org.apache.ibatis.parsing.XNode; +import org.apache.ibatis.scripting.defaults.RawLanguageDriver; +import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.logging.slf4j.SLF4JLogger; +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.TypeReference; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; + +/** + * Register invariant hints for Mybatis that are presumed the same for all applications + * + * @author Josh Long + */ +class MybatisGlobalBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor { + + private final PathMatchingResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); + + private final Logger log = LoggerFactory.getLogger(getClass()); + + MybatisGlobalBeanFactoryInitializationAotProcessor() { + } + + private void registerProxies(RuntimeHints hints) { + var proxies = Set.of(Set.of(Connection.class.getName()), Set.of(SqlSession.class.getName()), + Set.of(PreparedStatement.class.getName(), CallableStatement.class.getName()), + Set.of(ParameterizedType.class.getName(), + "org.springframework.core.SerializableTypeWrapper$SerializableTypeProxy", + Serializable.class.getName()), + Set.of(TypeVariable.class.getName(), + "org.springframework.core.SerializableTypeWrapper$SerializableTypeProxy", + Serializable.class.getName()), + Set.of(WildcardType.class.getName(), + "org.springframework.core.SerializableTypeWrapper$SerializableTypeProxy", + Serializable.class.getName())); + FlowableSpringAotUtils.debug("global proxies", proxies); + for (var p : proxies) { + var parts = p.stream().map(TypeReference::of).toArray(TypeReference[]::new); + hints.proxies().registerJdkProxy(parts); + } + } + + private static Resource newResourceFor(Resource in) { + try { + var marker = "jar!"; + var p = in.getURL().toExternalForm(); + var rest = p.substring(p.lastIndexOf(marker) + marker.length()); + return new ClassPathResource(rest); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void registerResources(RuntimeHints hints) throws IOException { + + var resources = new HashSet(); + var config = Stream + .of("org/apache/ibatis/builder/xml/*.dtd", "org/apache/ibatis/builder/xml/*.xsd", + "org/mybatis/spring/config/*.xsd") + .map(p -> ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + p) + .flatMap(p -> { + try { + return Stream.of(this.resourcePatternResolver.getResources(p)); + } + catch (IOException e) { + throw new RuntimeException(e); + } + }) + .map(MybatisGlobalBeanFactoryInitializationAotProcessor::newResourceFor) + .filter(Resource::exists) + .toList(); + + resources.addAll(config); + + FlowableSpringAotUtils.debug("resources", resources); + for (var r : resources) + hints.resources().registerResource(r); + } + + private void registerGlobalTypeHints(RuntimeHints hints) { + + var caches = Set.of(Cache.class, LruCache.class, BlockingCache.class, SerializedCache.class, FifoCache.class, + NullCacheKey.class, PerpetualCache.class, CacheKey.class, WeakCache.class, TransactionalCache.class, + SynchronizedCache.class, LoggingCache.class); + + var collections = Set.of(AbstractList.class, List.class, RandomAccess.class, Cloneable.class, Collection.class, + TreeSet.class, SortedSet.class, Iterator.class, ArrayList.class, HashSet.class, Set.class, Map.class); + + var loggers = Set.of(Log4jImpl.class, Log4j2Impl.class, Log4j2LoggerImpl.class, Log4j2AbstractLoggerImpl.class, + NoLoggingImpl.class, SLF4JLogger.class, StdOutImpl.class, BaseJdbcLogger.class, ConnectionLogger.class, + PreparedStatementLogger.class, ResultSetLogger.class, StatementLogger.class, Jdk14LoggingImpl.class, + JakartaCommonsLoggingImpl.class, Slf4jImpl.class); + + var annotations = Set.of(Select.class, Update.class, Insert.class, Delete.class, SelectProvider.class, + UpdateProvider.class, InsertProvider.class, CacheNamespace.class, Flush.class, DeleteProvider.class, + Options.class, Options.FlushCachePolicy.class, Many.class, Mapper.class, One.class, Property.class, + Result.class, Results.class); + + var memberCategories = MemberCategory.values(); + + var classesForReflection = new HashSet>(); + + classesForReflection.addAll(caches); + classesForReflection.addAll(annotations); + classesForReflection.addAll(loggers); + classesForReflection.addAll(collections); + + // Original version: +// classesForReflection.addAll(Set.of(Serializable.class, SpringBootVFS.class, PerpetualCache.class, Cursor.class, +// Optional.class, LruCache.class, MethodHandles.class, Date.class, HashMap.class, CacheRefResolver.class, +// XNode.class, ResultFlag.class, ResultMapResolver.class, MapperScannerConfigurer.class, +// MethodResolver.class, ProviderMethodResolver.class, ProviderContext.class, +// MapperAnnotationBuilder.class, Logger.class, LogFactory.class, RuntimeSupport.class, Log.class, +// SqlSessionTemplate.class, SqlSessionFactory.class, SqlSessionFactoryBean.class, ProxyFactory.class, +// XMLLanguageDriver.class, RawLanguageDriver.class, Configuration.class, String.class, int.class, +// Number.class, Integer.class, long.class, Long.class, short.class, Short.class, byte.class, Byte.class, +// float.class, Float.class, boolean.class, Boolean.class, double.class, Double.class)); + + classesForReflection.addAll(Set.of(Serializable.class, PerpetualCache.class, Cursor.class, + Optional.class, LruCache.class, MethodHandles.class, Date.class, HashMap.class, CacheRefResolver.class, + XNode.class, ResultFlag.class, ResultMapResolver.class, + MethodResolver.class, ProviderMethodResolver.class, ProviderContext.class, + MapperAnnotationBuilder.class, Logger.class, LogFactory.class, RuntimeSupport.class, Log.class, + SqlSessionFactory.class, ProxyFactory.class, + XMLLanguageDriver.class, RawLanguageDriver.class, Configuration.class, String.class, int.class, + Number.class, Integer.class, long.class, Long.class, short.class, Short.class, byte.class, Byte.class, + float.class, Float.class, boolean.class, Boolean.class, double.class, Double.class)); + + FlowableSpringAotUtils.debug("global types for reflection", classesForReflection); + + for (var c : classesForReflection) { + hints.reflection().registerType(c, memberCategories); + if (FlowableSpringAotUtils.isSerializable(c)) { + hints.serialization().registerType(TypeReference.of(c.getName())); + if (log.isDebugEnabled()) + log.debug("the type " + c.getName() + " is serializable"); + } + } + } + + @Override + public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { + return (generationContext, beanFactoryInitializationCode) -> { + try { + var hints = generationContext.getRuntimeHints(); + registerResources(hints); + registerGlobalTypeHints(hints); + registerProxies(hints); + } // + catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + }; + } + +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisMapperTypeUtils.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisMapperTypeUtils.java new file mode 100644 index 00000000000..89d6e7dec95 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisMapperTypeUtils.java @@ -0,0 +1,63 @@ +/* + * Copyright 2022 the original author or authors. + * + * 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 + * + * https://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.flowable.spring.aot; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.ibatis.reflection.TypeParameterResolver; + +/** + * @author Josh Long + */ +final class MybatisMapperTypeUtils { + + + static Class resolveReturnClass(Class mapperInterface, Method method) { + var resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface); + return typeToClass(resolvedReturnType, method.getReturnType()); + } + + static Set> resolveParameterClasses(Class mapperInterface, Method method) { + return Stream.of(TypeParameterResolver.resolveParamTypes(method, mapperInterface)) + .map(x -> typeToClass(x, x instanceof Class ? (Class) x : Object.class)) + .collect(Collectors.toSet()); + } + + private static Class typeToClass(Type src, Class fallback) { + var result = (Class) null; + if (src instanceof Class c) { + result = c.isArray() ? c.getComponentType() : c; + } + else if (src instanceof ParameterizedType parameterizedType) { + var index = (parameterizedType.getRawType() instanceof Class + && Map.class.isAssignableFrom((Class) parameterizedType.getRawType()) + && parameterizedType.getActualTypeArguments().length > 1) ? 1 : 0; + var actualType = parameterizedType.getActualTypeArguments()[index]; + result = typeToClass(actualType, fallback); + } + if (result == null) { + result = fallback; + } + return result; + } + +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisMappersBeanFactoryInitializationAotProcessor.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisMappersBeanFactoryInitializationAotProcessor.java new file mode 100644 index 00000000000..eb626b8ae57 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisMappersBeanFactoryInitializationAotProcessor.java @@ -0,0 +1,133 @@ +/* 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. + */ +package org.flowable.spring.aot; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.xml.parsers.DocumentBuilderFactory; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.autoconfigure.AutoConfigurationPackages; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.util.FileCopyUtils; +import org.w3c.dom.Element; +import org.xml.sax.InputSource; + +/** + * Discovers any {@literal mappings.xml} and reads them in to then register the + * referenced {@literal .xml} files as resource hints. + * + * @author Josh Long + */ +class MybatisMappersBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor { + + private final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + + MybatisMappersBeanFactoryInitializationAotProcessor() { + } + + private Set persistenceResources(String rootPackage) throws Exception { + var folderFromPackage = FlowableSpringAotUtils.packageToPath(rootPackage); + var patterns = Stream// + .of(folderFromPackage + "/**/mappings.xml")// + .map(path -> ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + path)// + .flatMap(p -> { + try { + return Stream.of(this.resolver.getResources(p)); + } // + catch (IOException e) { + throw new RuntimeException(e); + } + })// + .map(FlowableSpringAotUtils::newResourceFor) + .toList(); + + var resources = new HashSet(); + for (var p : patterns) { + var mappers = mappers(p); + resources.add(p); + resources.addAll(mappers); + } + return resources.stream().filter(Resource::exists).collect(Collectors.toSet()); + } + + protected List getPackagesToScan (BeanFactory b){ + return AutoConfigurationPackages.get(b) ; + } + + @Override + public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { + try { + var packages = getPackagesToScan(beanFactory); + var resources = new HashSet(); + for (var pkg : packages) { + resources.addAll(persistenceResources(pkg)); + } + return (generationContext, beanFactoryInitializationCode) -> { + for (var r : resources) + if (r.exists()) + generationContext.getRuntimeHints().resources().registerResource(r); + }; + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + private Set mappers(Resource mapping) throws Exception { + var resources = new HashSet(); + try (var in = new InputStreamReader(mapping.getInputStream())) { + var xml = FileCopyUtils.copyToString(in); + resources.addAll(mapperResources(xml)); + } + resources.add(mapping); + return resources; + + } + + private Set mapperResources(String xml) { + try { + var set = new HashSet(); + var dbf = DocumentBuilderFactory.newInstance(); + var db = dbf.newDocumentBuilder(); + var is = new InputSource(new StringReader(xml)); + var doc = db.parse(is); + var mappersElement = (Element) doc.getElementsByTagName("mappers").item(0); + var mapperList = mappersElement.getElementsByTagName("mapper"); + for (var i = 0; i < mapperList.getLength(); i++) { + var mapperElement = (Element) mapperList.item(i); + var resourceValue = mapperElement.getAttribute("resource"); + set.add(new ClassPathResource(resourceValue)); + } + return set; + } // + catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + + } + +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000000..6fbe8ed6816 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,5 @@ +org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\ +org.flowable.spring.aot.FlowableBeanFactoryInitializationAotProcessor,\ +org.flowable.spring.aot.FlowableMybatisMappersBeanFactoryInitializationAotProcessor,\ +org.flowable.spring.aot.MybatisGlobalBeanFactoryInitializationAotProcessor,\ +org.flowable.spring.aot.MybatisApplicationSpecificBeanFactoryInitializationAotProcessor \ No newline at end of file From 2caa16f0772f2b33698d2fd9de224acb695c0bc7 Mon Sep 17 00:00:00 2001 From: Joram Barrez Date: Tue, 5 Dec 2023 16:24:35 +0100 Subject: [PATCH 02/10] GraalVM throws an UnsupportedException, which isn't caught by the IOException in CommonAutoDeploymentStrategy --- .../flowable/common/spring/CommonAutoDeploymentStrategy.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/flowable-spring-common/src/main/java/org/flowable/common/spring/CommonAutoDeploymentStrategy.java b/modules/flowable-spring-common/src/main/java/org/flowable/common/spring/CommonAutoDeploymentStrategy.java index 7c5de34b0c1..9c008133b0d 100644 --- a/modules/flowable-spring-common/src/main/java/org/flowable/common/spring/CommonAutoDeploymentStrategy.java +++ b/modules/flowable-spring-common/src/main/java/org/flowable/common/spring/CommonAutoDeploymentStrategy.java @@ -13,7 +13,6 @@ package org.flowable.common.spring; -import java.io.IOException; import java.time.Duration; import java.util.Arrays; @@ -123,7 +122,7 @@ protected String determineResourceName(final Resource resource) { } else { try { resourceName = resource.getFile().getAbsolutePath(); - } catch (IOException e) { + } catch (Exception e) { // Catching any exception here, as e.g. Graal will throw an UnsupportedException instead of an IOException resourceName = resource.getFilename(); } } From d14c89338024137f42442048c75c12858f96f41e Mon Sep 17 00:00:00 2001 From: Joram Barrez Date: Sun, 7 Jan 2024 11:30:23 +0100 Subject: [PATCH 03/10] Spring Boot AOT - add cmmn/dmn resources --- .../deployer/CaseDefinitionDiagramHelper.java | 2 +- ...BeanFactoryInitializationAotProcessor.java | 122 +++++++++++++----- 2 files changed, 89 insertions(+), 35 deletions(-) diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/deployer/CaseDefinitionDiagramHelper.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/deployer/CaseDefinitionDiagramHelper.java index e3d59151aff..38d9e5bdb3f 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/deployer/CaseDefinitionDiagramHelper.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/deployer/CaseDefinitionDiagramHelper.java @@ -78,7 +78,7 @@ public boolean shouldCreateDiagram(CaseDefinitionEntity caseDefinition, EngineDe && CommandContextUtil.getCmmnEngineConfiguration().isCreateDiagramOnDeploy()) { // If the 'getProcessDiagramResourceNameFromDeployment' call returns null, it means - // no diagram image for the process definition was provided in the deployment resources. + // no diagram image for the case definition was provided in the deployment resources. return ResourceNameUtil.getCaseDiagramResourceNameFromDeployment(caseDefinition, deployment.getResources()) == null; } diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableBeanFactoryInitializationAotProcessor.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableBeanFactoryInitializationAotProcessor.java index 5f67935d7ab..1316c0ee618 100644 --- a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableBeanFactoryInitializationAotProcessor.java +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableBeanFactoryInitializationAotProcessor.java @@ -26,6 +26,7 @@ import org.flowable.bpmn.converter.BpmnXMLConverter; import org.flowable.bpmn.model.ImplementationType; import org.flowable.bpmn.model.ServiceTask; +import org.flowable.cmmn.converter.CmmnXmlConverter; import org.flowable.common.engine.api.query.Query; import org.flowable.common.engine.impl.db.ListQueryParameterObject; import org.flowable.common.engine.impl.de.odysseus.el.ExpressionFactoryImpl; @@ -41,6 +42,7 @@ 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.TypeReference; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; @@ -68,14 +70,25 @@ class FlowableBeanFactoryInitializationAotProcessor implements BeanFactoryInitia private Set processResources() { - return resources("processes/**/*.bpmn20.xml"); + return resources("processes/**/*.bpmn20.xml", "processes/**/*.bpmn20"); + } + + private Set caseResources() { + return resources("cases/**/*.cmmn", "cases/**/*.cmmn.xml"); + } + + private Set ruleResources() { + return resources("dmn/**/*.dmn", "cases/**/*.dmn.xml"); } private Set flowablePersistenceResources() throws Exception { var resources = new HashSet(); resources.addAll(resources("org/flowable/**/*.sql", "org/flowable/**/*.xml", "org/flowable/**/*.txt", "org/flowable/**/*.xsd", "org/flowable/**/*.properties")); + resources.addAll(processResources()); + resources.addAll(caseResources()); + resources.addAll(ruleResources()); for (var e : "xml,yaml,yml".split(",")) resources.add(new ClassPathResource("flowable-default." + e)); @@ -158,39 +171,8 @@ public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableL } - // here lay dragons; we're going to attempt to proactively register aot hints for beans referenced within a process definition - var processDefinitions = this.processResources(); - for (var processDefinitionXmlResource : processDefinitions) { - Assert.state(processDefinitionXmlResource.exists(), "the process definition file [" + processDefinitionXmlResource.getFilename() + - "] does not exist"); - - hints.resources().registerResource(processDefinitionXmlResource); - try (var in = processDefinitionXmlResource.getInputStream()) { - - var bpmnXMLConverter = new BpmnXMLConverter(); - var bpmnModel = bpmnXMLConverter.convertToBpmnModel(() -> in, false, false); - var serviceTasks = bpmnModel.getMainProcess().findFlowElementsOfType(ServiceTask.class); - for (var st : serviceTasks) { - if (st.getImplementationType().equals(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION)) { - var expression = st.getImplementation(); - var expressionWithoutDelimiters = expression.substring(2); - expressionWithoutDelimiters = expressionWithoutDelimiters.substring(0, expressionWithoutDelimiters.length() - 1); - var beanName = expressionWithoutDelimiters; - try { - var beanDefinition = beanFactory.getBeanDefinition(beanName); - hints.reflection().registerType(TypeReference.of(beanDefinition.getBeanClassName()), MemberCategory.values()); - - log.debug("registering hint for bean name [" + beanName + "]"); - }// - catch (Throwable throwable) { - log.error("couldn't find bean named [" + beanName + "]"); - } - - } - } - } - } - + // here lay dragons; we're going to attempt to proactively register aot hints for beans referenced within definitions + registerAotHintsForReferencedBeans(beanFactory, hints); }// catch (Throwable throwable) { @@ -199,6 +181,78 @@ public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableL }; } + private void registerAotHintsForReferencedBeans(ConfigurableListableBeanFactory beanFactory, RuntimeHints hints) throws IOException { + registerAotHintForProcessDefinitionBeans(beanFactory, hints); + registerAotHintForCaseDefinitionBeans(beanFactory, hints); + } + + protected void registerAotHintForProcessDefinitionBeans(ConfigurableListableBeanFactory beanFactory, RuntimeHints hints) throws IOException { + var processDefinitions = this.processResources(); + for (var processDefinitionXmlResource : processDefinitions) { + Assert.state(processDefinitionXmlResource.exists(), "the process definition file [" + processDefinitionXmlResource.getFilename() + + "] does not exist"); + + hints.resources().registerResource(processDefinitionXmlResource); + try (var in = processDefinitionXmlResource.getInputStream()) { + + var bpmnXMLConverter = new BpmnXMLConverter(); + var bpmnModel = bpmnXMLConverter.convertToBpmnModel(() -> in, false, false); + var serviceTasks = bpmnModel.getMainProcess().findFlowElementsOfType(ServiceTask.class); + for (var serviceTask : serviceTasks) { + if (serviceTask.getImplementationType().equals(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION)) { + var expression = serviceTask.getImplementation(); + var expressionWithoutDelimiters = expression.substring(2); + expressionWithoutDelimiters = expressionWithoutDelimiters.substring(0, expressionWithoutDelimiters.length() - 1); + var beanName = expressionWithoutDelimiters; + try { + var beanDefinition = beanFactory.getBeanDefinition(beanName); + hints.reflection().registerType(TypeReference.of(beanDefinition.getBeanClassName()), MemberCategory.values()); + + log.debug("registering hint for bean name [" + beanName + "]"); + } + catch (Throwable throwable) { + log.error("couldn't find bean named [" + beanName + "]"); + } + + } + } + } + } + } + + protected void registerAotHintForCaseDefinitionBeans(ConfigurableListableBeanFactory beanFactory, RuntimeHints hints) throws IOException { + var caseDefinitions = this.caseResources(); + for (var caseDefinitionXmlResource : caseDefinitions) { + Assert.state(caseDefinitionXmlResource.exists(), "the case definition file [" + caseDefinitionXmlResource.getFilename() + + "] does not exist"); + + hints.resources().registerResource(caseDefinitionXmlResource); + try (var in = caseDefinitionXmlResource.getInputStream()) { + + var cmmXmlConverter = new CmmnXmlConverter(); + var cmmnModel = cmmXmlConverter.convertToCmmnModel(() -> in, false, false); + var serviceTasks = cmmnModel.getPrimaryCase().findPlanItemDefinitionsOfType(org.flowable.cmmn.model.ServiceTask.class); + for (var serviceTask : serviceTasks) { + if (serviceTask.getImplementationType().equals(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION)) { + var expression = serviceTask.getImplementation(); + var expressionWithoutDelimiters = expression.substring(2); + expressionWithoutDelimiters = expressionWithoutDelimiters.substring(0, expressionWithoutDelimiters.length() - 1); + var beanName = expressionWithoutDelimiters; + try { + var beanDefinition = beanFactory.getBeanDefinition(beanName); + hints.reflection().registerType(TypeReference.of(beanDefinition.getBeanClassName()), MemberCategory.values()); + + log.debug("registering hint for bean name [" + beanName + "]"); + } + catch (Throwable throwable) { + log.error("couldn't find bean named [" + beanName + "]"); + } + + } + } + } + } + } private static Set from(T[] t) { return new HashSet<>(Arrays.asList(t)); From 5a31cf818952d42433ee3a2b82d61062c579a0a8 Mon Sep 17 00:00:00 2001 From: Josh Long Date: Fri, 26 Jan 2024 10:18:40 +0100 Subject: [PATCH 04/10] Add base sample native example The example is from https://github.com/spring-tips/flowable-processes Co-Authored-By: Joram Barrez --- .../native.sh | 4 + .../pom.xml | 52 ++++++++ .../com/example/flowable/EmailService.java | 52 ++++++++ .../example/flowable/FlowableApplication.java | 116 ++++++++++++++++++ .../src/main/resources/application.properties | 9 ++ .../src/main/resources/cases/signupCase.cmmn | 66 ++++++++++ .../resources/dmn/dmn-myDecicisionTable.dmn | 38 ++++++ .../processes/single-task-process.bpmn20.xml | 31 +++++ .../flowable/FlowableApplicationTest.java | 25 ++++ .../flowable-spring-boot-samples/pom.xml | 1 + 10 files changed, 394 insertions(+) create mode 100755 modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/native.sh create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/pom.xml create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/java/com/example/flowable/EmailService.java create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/java/com/example/flowable/FlowableApplication.java create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/application.properties create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/cases/signupCase.cmmn create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/dmn/dmn-myDecicisionTable.dmn create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/processes/single-task-process.bpmn20.xml create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/test/java/com/example/flowable/FlowableApplicationTest.java diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/native.sh b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/native.sh new file mode 100755 index 00000000000..29a85ee759a --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/native.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +rm -rf target +mvn -DskipTests -Pnative native:compile +./target/flowable diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/pom.xml b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/pom.xml new file mode 100644 index 00000000000..cbaa70290d7 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/pom.xml @@ -0,0 +1,52 @@ + + 4.0.0 + + + org.flowable + flowable-spring-boot-samples + 7.1.0-SNAPSHOT + + + flowable-spring-boot-sample-native + + + + org.flowable + flowable-spring-boot-starter + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + + + + + org.graalvm.buildtools + native-maven-plugin + + + --enable-url-protocols=https + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + -agentlib:native-image-agent=config-output-dir=target/native-image + + + + + + + diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/java/com/example/flowable/EmailService.java b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/java/com/example/flowable/EmailService.java new file mode 100644 index 00000000000..d8d2c797f5c --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/java/com/example/flowable/EmailService.java @@ -0,0 +1,52 @@ +/* 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. + */ +package com.example.flowable; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.flowable.cmmn.api.delegate.DelegatePlanItemInstance; +import org.flowable.cmmn.api.delegate.PlanItemJavaDelegate; +import org.flowable.common.engine.api.variable.VariableContainer; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.JavaDelegate; +import org.springframework.stereotype.Service; + +@Service +public class EmailService implements JavaDelegate, PlanItemJavaDelegate { + + protected final ConcurrentHashMap sends = new ConcurrentHashMap<>(); + + protected AtomicInteger getSendCount(String key) { + return this.sends.get(key); + } + + @Override + public void execute(DelegateExecution execution) { + internalExecute(execution); + } + + @Override + public void execute(DelegatePlanItemInstance planItemInstance) { + internalExecute(planItemInstance); + } + + protected void internalExecute(VariableContainer variableContainer) { + String customerId = (String) variableContainer.getVariable("customerId"); + String email = (String) variableContainer.getVariable("email"); + System.out.println("sending welcome email for " + customerId + " to " + email); + sends.computeIfAbsent(email, e -> new AtomicInteger()); + sends.get(email).incrementAndGet(); + } + +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/java/com/example/flowable/FlowableApplication.java b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/java/com/example/flowable/FlowableApplication.java new file mode 100644 index 00000000000..a2202a1124c --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/java/com/example/flowable/FlowableApplication.java @@ -0,0 +1,116 @@ +/* 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. + */ +package com.example.flowable; + +import java.util.List; +import java.util.Map; + +import org.flowable.cmmn.api.CmmnRuntimeService; +import org.flowable.cmmn.api.CmmnTaskService; +import org.flowable.cmmn.engine.CmmnEngine; +import org.flowable.dmn.engine.DmnEngine; +import org.flowable.engine.ProcessEngine; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; +import org.flowable.task.api.Task; +import org.springframework.boot.ApplicationRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.util.Assert; + +@SpringBootApplication +public class FlowableApplication { + + public static void main(String[] args) { + SpringApplication.run(FlowableApplication.class, args); + } + + @Bean + ApplicationRunner demo(ProcessEngine processEngine, CmmnEngine cmmnEngine, DmnEngine dmnEngine, EmailService emailService) { + return args -> { + startProcessInstance(processEngine, emailService); + startCaseInstance(cmmnEngine, emailService); + executeRule(dmnEngine); + }; + } + + protected void startProcessInstance(ProcessEngine processEngine, EmailService emailService) { + String customerId = "1"; + String email = "email@email.com"; + + RuntimeService runtimeService = processEngine.getRuntimeService(); + TaskService taskService = processEngine.getTaskService(); + + Map vars = Map.of("customerId", customerId, "email", email); + String processInstanceId = runtimeService.startProcessInstanceByKey("signup-process", vars).getId(); + + System.out.println("process instance ID: " + processInstanceId); + Assert.notNull(processInstanceId, "the process instance ID should not be null"); + List tasks = taskService + .createTaskQuery() + .taskName("confirm-email-task") + .includeProcessVariables() + .processVariableValueEquals("customerId", customerId) + .list(); + Assert.state(!tasks.isEmpty(), "there should be one outstanding task"); + tasks.forEach(task -> { + taskService.claim(task.getId(), "jlong"); + taskService.complete(task.getId()); + }); + Assert.isTrue(emailService.getSendCount(email).get() == 1, "there should be 1 email sent"); + } + + protected void startCaseInstance(CmmnEngine cmmnEngine, EmailService emailService) { + String customerId = "2"; + String email = "email@email.com"; + + CmmnRuntimeService cmmnRuntimeService = cmmnEngine.getCmmnRuntimeService(); + CmmnTaskService cmmnTaskService = cmmnEngine.getCmmnTaskService(); + + String caseInstanceId = cmmnRuntimeService.createCaseInstanceBuilder() + .caseDefinitionKey("signupCase") + .variable("customerId", customerId) + .variable("email", email) + .start() + .getId(); + + System.out.println("case instance ID: " + caseInstanceId); + Assert.notNull(caseInstanceId, "the case instance ID should not be null"); + List tasks = cmmnTaskService + .createTaskQuery() + .taskName("Confirm email task") + .includeProcessVariables() + .caseVariableValueEquals("customerId", customerId) + .list(); + Assert.state(!tasks.isEmpty(), "there should be one outstanding task"); + tasks.forEach(task -> { + cmmnTaskService.claim(task.getId(), "jbarrez"); + cmmnTaskService.complete(task.getId()); + }); + Assert.isTrue(emailService.getSendCount(email).get() == 2, "there should be 2 emails sent"); + } + + protected void executeRule(DmnEngine dmnEngine) { + Map result = dmnEngine.getDmnDecisionService().createExecuteDecisionBuilder() + .decisionKey("myDecisionTable") + .variable("customerTotalOrderPrice", 99999) + .executeWithSingleResult(); + + Assert.isTrue(result.size() == 1, "Expected one result"); + Object tier = result.get("tier"); + Assert.isTrue(tier.equals("SILVER"), "Expected SILVER as output, but was " + tier); + System.out.println("Executed DMN rule correctly"); + } + +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/application.properties b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/application.properties new file mode 100644 index 00000000000..2740ffc144a --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/application.properties @@ -0,0 +1,9 @@ +#spring.threads.virtual.enabled=true + +logging.level.root=INFO + +spring.sql.init.mode=always + +flowable.eventregistry.enabled=false +flowable.idm.enabled=false +flowable.jpa-enabled=false diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/cases/signupCase.cmmn b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/cases/signupCase.cmmn new file mode 100644 index 00000000000..251e51a5a41 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/cases/signupCase.cmmn @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + complete + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/dmn/dmn-myDecicisionTable.dmn b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/dmn/dmn-myDecicisionTable.dmn new file mode 100644 index 00000000000..31555a837ad --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/dmn/dmn-myDecicisionTable.dmn @@ -0,0 +1,38 @@ + + + + + + customerTotalOrderPrice + + + + + + 100000]]> + + + + + + + + 10000]]> + + + + + + + + 1000]]> + + + + + + + + + diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/processes/single-task-process.bpmn20.xml b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/processes/single-task-process.bpmn20.xml new file mode 100644 index 00000000000..1c7ead951e2 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/processes/single-task-process.bpmn20.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/test/java/com/example/flowable/FlowableApplicationTest.java b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/test/java/com/example/flowable/FlowableApplicationTest.java new file mode 100644 index 00000000000..c7139957e83 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/test/java/com/example/flowable/FlowableApplicationTest.java @@ -0,0 +1,25 @@ +/* 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. + */ +package com.example.flowable; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class FlowableApplicationTest { + + @Test + void contextStarts() { + + } +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/pom.xml b/modules/flowable-spring-boot/flowable-spring-boot-samples/pom.xml index 8091a142ac9..cbbbf4b3e01 100644 --- a/modules/flowable-spring-boot/flowable-spring-boot-samples/pom.xml +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/pom.xml @@ -31,6 +31,7 @@ flowable-spring-boot-sample-dmn flowable-spring-boot-sample-cmmn flowable-spring-boot-sample-ldap + flowable-spring-boot-sample-native flowable-spring-boot-sample-starter From 92c5d4d8dd1af5d81218534bd3c8cfe49c703496 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 28 Jan 2024 01:12:47 +0100 Subject: [PATCH 05/10] Split Native Runtime Hints generation in appropriate packages + try to register types using the mappings.xml --- .github/workflows/graal-native.yml | 33 ++ .../impl/aot/FlowableAppRuntimeHints.java | 31 ++ .../resources/META-INF/spring/aot.factories | 2 + .../aot/FlowableBatchServiceRuntimeHints.java | 31 ++ .../resources/META-INF/spring/aot.factories | 2 + .../impl/aot/FlowableCmmnRuntimeHints.java | 38 +++ .../resources/META-INF/spring/aot.factories | 2 + .../impl/aot/FlowableDmnRuntimeHints.java | 32 ++ .../resources/META-INF/spring/aot.factories | 2 + .../impl/aot/FlowableCommonRuntimeHints.java | 39 +++ ...FlowableMyBatisResourceHintsRegistrar.java | 106 ++++++ .../impl/aot/FlowableMyBatisRuntimeHints.java | 73 +++++ .../FlowableSqlResourceHintsRegistrar.java | 31 ++ .../resources/META-INF/spring/aot.factories | 3 + .../impl/aot/FlowableProcessRuntimeHints.java | 39 +++ .../resources/META-INF/spring/aot.factories | 2 + ...FlowableEntityLinkServiceRuntimeHints.java | 31 ++ .../resources/META-INF/spring/aot.factories | 2 + .../FlowableEventRegistryRuntimeHints.java | 41 +++ .../resources/META-INF/spring/aot.factories | 2 + ...eEventSubscriptionServiceRuntimeHints.java | 30 ++ .../resources/META-INF/spring/aot.factories | 3 + ...owableIdentityLinkServiceRuntimeHints.java | 30 ++ .../resources/META-INF/spring/aot.factories | 2 + .../aot/FlowableJobServiceRuntimeHints.java | 30 ++ .../resources/META-INF/spring/aot.factories | 2 + .../native.sh | 2 +- .../pom.xml | 121 ++++++- .../src/main/resources/application.properties | 1 - ...BeanFactoryInitializationAotProcessor.java | 304 ------------------ ...BeanFactoryInitializationAotProcessor.java | 38 --- .../spring/aot/FlowableSpringAotUtils.java | 85 ----- ...BeanFactoryInitializationAotProcessor.java | 169 ---------- ...BeanFactoryInitializationAotProcessor.java | 262 --------------- .../spring/aot/MybatisMapperTypeUtils.java | 63 ---- ...BeanFactoryInitializationAotProcessor.java | 133 -------- .../BaseAutoDeployResourceContribution.java | 100 ++++++ ...BeanFactoryInitializationAotProcessor.java | 122 +++++++ ...BeanFactoryInitializationAotProcessor.java | 63 ++++ ...BeanFactoryInitializationAotProcessor.java | 122 +++++++ ...DefaultPropertiesLocationRuntimeHints.java | 65 ++++ .../resources/META-INF/spring/aot.factories | 10 +- .../aot/FlowableTaskServiceRuntimeHints.java | 30 ++ .../resources/META-INF/spring/aot.factories | 2 + .../FlowableVariableServiceRuntimeHints.java | 34 ++ .../resources/META-INF/spring/aot.factories | 2 + pom.xml | 7 + 47 files changed, 1312 insertions(+), 1062 deletions(-) create mode 100644 .github/workflows/graal-native.yml create mode 100644 modules/flowable-app-engine/src/main/java/org/flowable/app/engine/impl/aot/FlowableAppRuntimeHints.java create mode 100644 modules/flowable-app-engine/src/main/resources/META-INF/spring/aot.factories create mode 100644 modules/flowable-batch-service/src/main/java/org/flowable/batch/service/impl/aot/FlowableBatchServiceRuntimeHints.java create mode 100644 modules/flowable-batch-service/src/main/resources/META-INF/spring/aot.factories create mode 100644 modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/aot/FlowableCmmnRuntimeHints.java create mode 100644 modules/flowable-cmmn-engine/src/main/resources/META-INF/spring/aot.factories create mode 100644 modules/flowable-dmn-engine/src/main/java/org/flowable/dmn/engine/impl/aot/FlowableDmnRuntimeHints.java create mode 100644 modules/flowable-dmn-engine/src/main/resources/META-INF/spring/aot.factories create mode 100644 modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableCommonRuntimeHints.java create mode 100644 modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableMyBatisResourceHintsRegistrar.java create mode 100644 modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableMyBatisRuntimeHints.java create mode 100644 modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableSqlResourceHintsRegistrar.java create mode 100644 modules/flowable-engine-common/src/main/resources/META-INF/spring/aot.factories create mode 100644 modules/flowable-engine/src/main/java/org/flowable/engine/impl/aot/FlowableProcessRuntimeHints.java create mode 100644 modules/flowable-engine/src/main/resources/META-INF/spring/aot.factories create mode 100644 modules/flowable-entitylink-service/src/main/java/org/flowable/entitylink/service/impl/aot/FlowableEntityLinkServiceRuntimeHints.java create mode 100644 modules/flowable-entitylink-service/src/main/resources/META-INF/spring/aot.factories create mode 100644 modules/flowable-event-registry/src/main/java/org/flowable/eventregistry/impl/aot/FlowableEventRegistryRuntimeHints.java create mode 100644 modules/flowable-event-registry/src/main/resources/META-INF/spring/aot.factories create mode 100644 modules/flowable-eventsubscription-service/src/main/java/org/flowable/eventsubscription/service/impl/aot/FlowableEventSubscriptionServiceRuntimeHints.java create mode 100644 modules/flowable-eventsubscription-service/src/main/resources/META-INF/spring/aot.factories create mode 100644 modules/flowable-identitylink-service/src/main/java/org/flowable/identitylink/service/impl/aot/FlowableIdentityLinkServiceRuntimeHints.java create mode 100644 modules/flowable-identitylink-service/src/main/resources/META-INF/spring/aot.factories create mode 100644 modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/aot/FlowableJobServiceRuntimeHints.java create mode 100644 modules/flowable-job-service/src/main/resources/META-INF/spring/aot.factories delete mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableBeanFactoryInitializationAotProcessor.java delete mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableMybatisMappersBeanFactoryInitializationAotProcessor.java delete mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableSpringAotUtils.java delete mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisApplicationSpecificBeanFactoryInitializationAotProcessor.java delete mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisGlobalBeanFactoryInitializationAotProcessor.java delete mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisMapperTypeUtils.java delete mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisMappersBeanFactoryInitializationAotProcessor.java create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/BaseAutoDeployResourceContribution.java create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/cmmn/FlowableCmmnAutoDeployBeanFactoryInitializationAotProcessor.java create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/dmn/FlowableDmnAutoDeployBeanFactoryInitializationAotProcessor.java create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/process/FlowableProcessAutoDeployBeanFactoryInitializationAotProcessor.java create mode 100644 modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/environment/FlowableDefaultPropertiesLocationRuntimeHints.java create mode 100644 modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/aot/FlowableTaskServiceRuntimeHints.java create mode 100644 modules/flowable-task-service/src/main/resources/META-INF/spring/aot.factories create mode 100644 modules/flowable-variable-service/src/main/java/org/flowable/variable/service/impl/aot/FlowableVariableServiceRuntimeHints.java create mode 100644 modules/flowable-variable-service/src/main/resources/META-INF/spring/aot.factories diff --git a/.github/workflows/graal-native.yml b/.github/workflows/graal-native.yml new file mode 100644 index 00000000000..82ffd221e6e --- /dev/null +++ b/.github/workflows/graal-native.yml @@ -0,0 +1,33 @@ +name: Flowable Graal Build + +on: + push: + branches: + - main + - 'flowable-release-*' + +env: + MAVEN_ARGS: >- + -B -V --no-transfer-progress + -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120 + +jobs: + test_graal: + name: Linux Graal Native + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: graalvm/setup-graalvm@v1 + with: + java-version: 17 + distribution: graalvm + - name: Cache Maven Repository + uses: actions/cache@v3 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Install + run: ./mvnw install -Pdistro ${MAVEN_ARGS} -DskipTests=true -Dmaven.javadoc.skip=true + - name: Test + run: ./mvnw test -PnativeTest,native,distro,errorLogging ${MAVEN_ARGS} -Dmaven.test.redirectTestOutputToFile=false -pl modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native diff --git a/modules/flowable-app-engine/src/main/java/org/flowable/app/engine/impl/aot/FlowableAppRuntimeHints.java b/modules/flowable-app-engine/src/main/java/org/flowable/app/engine/impl/aot/FlowableAppRuntimeHints.java new file mode 100644 index 00000000000..76235896c78 --- /dev/null +++ b/modules/flowable-app-engine/src/main/java/org/flowable/app/engine/impl/aot/FlowableAppRuntimeHints.java @@ -0,0 +1,31 @@ +/* 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. + */ +package org.flowable.app.engine.impl.aot; + +import org.flowable.common.engine.impl.aot.FlowableMyBatisResourceHintsRegistrar; +import org.springframework.aot.hint.ResourceHints; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; + +/** + * @author Filip Hrisafov + */ +public class FlowableAppRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + ResourceHints resourceHints = hints.resources(); + resourceHints.registerPattern("org/flowable/app/db/liquibase/flowable-app-db-changelog.xml"); + FlowableMyBatisResourceHintsRegistrar.registerMappingResources("org/flowable/app/db/mapping", hints, classLoader); + } +} diff --git a/modules/flowable-app-engine/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-app-engine/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000000..4c43ba98c20 --- /dev/null +++ b/modules/flowable-app-engine/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.flowable.app.engine.impl.aot.FlowableAppRuntimeHints diff --git a/modules/flowable-batch-service/src/main/java/org/flowable/batch/service/impl/aot/FlowableBatchServiceRuntimeHints.java b/modules/flowable-batch-service/src/main/java/org/flowable/batch/service/impl/aot/FlowableBatchServiceRuntimeHints.java new file mode 100644 index 00000000000..baba6d34262 --- /dev/null +++ b/modules/flowable-batch-service/src/main/java/org/flowable/batch/service/impl/aot/FlowableBatchServiceRuntimeHints.java @@ -0,0 +1,31 @@ +/* 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. + */ +package org.flowable.batch.service.impl.aot; + +import org.flowable.common.engine.impl.aot.FlowableSqlResourceHintsRegistrar; +import org.springframework.aot.hint.ResourceHints; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; + +/** + * @author Filip Hrisafov + */ +public class FlowableBatchServiceRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + ResourceHints resourceHints = hints.resources(); + FlowableSqlResourceHintsRegistrar.registerSqlResources("org/flowable/batch/service/db", resourceHints); + + } +} diff --git a/modules/flowable-batch-service/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-batch-service/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000000..2a04bc04f27 --- /dev/null +++ b/modules/flowable-batch-service/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.flowable.batch.service.impl.aot.FlowableBatchServiceRuntimeHints diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/aot/FlowableCmmnRuntimeHints.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/aot/FlowableCmmnRuntimeHints.java new file mode 100644 index 00000000000..60821021edd --- /dev/null +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/aot/FlowableCmmnRuntimeHints.java @@ -0,0 +1,38 @@ +/* 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. + */ +package org.flowable.cmmn.engine.impl.aot; + +import org.flowable.common.engine.impl.aot.FlowableMyBatisResourceHintsRegistrar; +import org.flowable.variable.service.impl.QueryVariableValue; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.ResourceHints; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; + +/** + * @author Filip Hrisafov + */ +public class FlowableCmmnRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + ResourceHints resourceHints = hints.resources(); + FlowableMyBatisResourceHintsRegistrar.registerMappingResources("org/flowable/cmmn/db/mapping", hints, classLoader); + resourceHints.registerPattern("org/flowable/cmmn/db/liquibase/flowable-cmmn-db-changelog.xml"); + resourceHints.registerPattern("org/flowable/cmmn/db/liquibase/flowable-cmmn-db-changelog-crdb.xml"); + resourceHints.registerPattern("org/flowable/impl/cmmn/parser/*.xsd"); + + hints.reflection() + .registerType(QueryVariableValue.class, MemberCategory.INVOKE_PUBLIC_METHODS); + } +} diff --git a/modules/flowable-cmmn-engine/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-cmmn-engine/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000000..092b938353d --- /dev/null +++ b/modules/flowable-cmmn-engine/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.flowable.cmmn.engine.impl.aot.FlowableCmmnRuntimeHints diff --git a/modules/flowable-dmn-engine/src/main/java/org/flowable/dmn/engine/impl/aot/FlowableDmnRuntimeHints.java b/modules/flowable-dmn-engine/src/main/java/org/flowable/dmn/engine/impl/aot/FlowableDmnRuntimeHints.java new file mode 100644 index 00000000000..f3c0e982531 --- /dev/null +++ b/modules/flowable-dmn-engine/src/main/java/org/flowable/dmn/engine/impl/aot/FlowableDmnRuntimeHints.java @@ -0,0 +1,32 @@ +/* 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. + */ +package org.flowable.dmn.engine.impl.aot; + +import org.flowable.common.engine.impl.aot.FlowableMyBatisResourceHintsRegistrar; +import org.springframework.aot.hint.ResourceHints; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; + +/** + * @author Filip Hrisafov + */ +public class FlowableDmnRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + ResourceHints resourceHints = hints.resources(); + FlowableMyBatisResourceHintsRegistrar.registerMappingResources("org/flowable/dmn/db/mapping", hints, classLoader); + resourceHints.registerPattern("org/flowable/dmn/db/liquibase/flowable-dmn-db-changelog.xml"); + resourceHints.registerPattern("org/flowable/impl/dmn/parser/*.xsd"); + } +} diff --git a/modules/flowable-dmn-engine/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-dmn-engine/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000000..50cfebdb05a --- /dev/null +++ b/modules/flowable-dmn-engine/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.flowable.dmn.engine.impl.aot.FlowableDmnRuntimeHints diff --git a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableCommonRuntimeHints.java b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableCommonRuntimeHints.java new file mode 100644 index 00000000000..05eb7abefe1 --- /dev/null +++ b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableCommonRuntimeHints.java @@ -0,0 +1,39 @@ +/* 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. + */ +package org.flowable.common.engine.impl.aot; + +import org.flowable.common.engine.impl.persistence.entity.ByteArrayRefTypeHandler; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.ResourceHints; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; + +/** + * @author Filip Hrisafov + */ +public class FlowableCommonRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + ResourceHints resourceHints = hints.resources(); + resourceHints.registerPattern("META-INF/services/liquibase.hub.HubService"); + resourceHints.registerPattern("META-INF/services/liquibase.license.LicenseService"); + resourceHints.registerResourceBundle("org.flowable.common.engine.impl.de.odysseus.el.misc.LocalStrings"); + // If we can detect which DB is being used we can perhaps register only the appropriate DB file + resourceHints.registerPattern("org/flowable/common/db/properties/*.properties"); + FlowableSqlResourceHintsRegistrar.registerSqlResources("org/flowable/common/db", resourceHints); + + hints.reflection() + .registerType(ByteArrayRefTypeHandler.class, MemberCategory.values()); + } +} diff --git a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableMyBatisResourceHintsRegistrar.java b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableMyBatisResourceHintsRegistrar.java new file mode 100644 index 00000000000..9c63ad0f8ef --- /dev/null +++ b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableMyBatisResourceHintsRegistrar.java @@ -0,0 +1,106 @@ +/* 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. + */ +package org.flowable.common.engine.impl.aot; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.ibatis.builder.xml.XMLMapperEntityResolver; +import org.apache.ibatis.parsing.XNode; +import org.apache.ibatis.parsing.XPathParser; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.ReflectionHints; +import org.springframework.aot.hint.ResourceHints; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.TypeReference; +import org.springframework.core.io.ClassPathResource; + +/** + * Register the necessary resource hints for the Flowable SQL resources. + * + * @author Filip Hrisafov + */ +public class FlowableMyBatisResourceHintsRegistrar { + + public static void registerMappingResources(String baseFolder, RuntimeHints runtimeHints, ClassLoader classLoader) { + ResourceHints resourceHints = runtimeHints.resources(); + String mappingsPath = baseFolder + "/mappings.xml"; + ClassPathResource mappingsResource = new ClassPathResource(mappingsPath); + resourceHints.registerResource(mappingsResource); + try (InputStream mappingsStream = mappingsResource.getInputStream()) { + XPathParser parser = createParser(mappingsStream); + + List mappers = parser.evalNodes("/configuration/mappers/mapper"); + for (XNode mapper : mappers) { + registerMapper(mapper.getStringAttribute("resource"), runtimeHints, classLoader); + } + } catch (IOException e) { + throw new UncheckedIOException("Failed to read mappings " + mappingsPath, e); + } + } + + public static void registerMapper(String mapperPath, RuntimeHints hints, ClassLoader classLoader) { + ResourceHints resourceHints = hints.resources(); + ClassPathResource mapperResource = new ClassPathResource(mapperPath); + resourceHints.registerResource(mapperResource); + + ReflectionHints reflectionHints = hints.reflection(); + MemberCategory[] memberCategories = MemberCategory.values(); + try (InputStream mapperStream = mapperResource.getInputStream()) { + XPathParser parser = createParser(mapperStream); + XNode mapper = parser.evalNode("/mapper"); + // The xpath resolving is similar like what MyBatis does in XMLMapperBuilder#parse + for (XNode resultMap : mapper.evalNodes("/mapper/resultMap")) { + String type = resultMap.getStringAttribute("type"); + if (type != null) { + reflectionHints.registerType(TypeReference.of(type), memberCategories); + } + } + + for (XNode statement : mapper.evalNodes("select|insert|update|delete")) { + String parameterType = statement.getStringAttribute("parameterType"); + if (parameterType != null) { + if (parameterType.startsWith("org.flowable") || parameterType.startsWith("java.")) { + reflectionHints.registerType(TypeReference.of(parameterType), memberCategories); + } else if (parameterType.equals("map")) { + reflectionHints.registerType(Map.class, memberCategories); + } + } + + String resultType = statement.getStringAttribute("resultType"); + if (resultType != null) { + if (resultType.equals("long")) { + reflectionHints.registerType(long.class, memberCategories); + reflectionHints.registerType(Long.class, memberCategories); + } else if (resultType.equals("string")) { + reflectionHints.registerType(String.class, memberCategories); + } else if (resultType.equals("map")) { + reflectionHints.registerType(HashMap.class, memberCategories); + } + } + } + + } catch (IOException e) { + throw new UncheckedIOException("Failed to read mapper from " + mapperPath, e); + } + } + + protected static XPathParser createParser(InputStream stream) { + return new XPathParser(stream, false, null, new XMLMapperEntityResolver()); + } + +} diff --git a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableMyBatisRuntimeHints.java b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableMyBatisRuntimeHints.java new file mode 100644 index 00000000000..06ad1a82183 --- /dev/null +++ b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableMyBatisRuntimeHints.java @@ -0,0 +1,73 @@ +/* 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. + */ +package org.flowable.common.engine.impl.aot; + +import org.apache.ibatis.cache.decorators.FifoCache; +import org.apache.ibatis.cache.decorators.LruCache; +import org.apache.ibatis.cache.decorators.SoftCache; +import org.apache.ibatis.cache.decorators.WeakCache; +import org.apache.ibatis.cache.impl.PerpetualCache; +import org.apache.ibatis.javassist.util.proxy.ProxyFactory; +import org.apache.ibatis.javassist.util.proxy.RuntimeSupport; +import org.apache.ibatis.logging.Log; +import org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl; +import org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl; +import org.apache.ibatis.logging.log4j2.Log4j2Impl; +import org.apache.ibatis.logging.nologging.NoLoggingImpl; +import org.apache.ibatis.logging.slf4j.Slf4jImpl; +import org.apache.ibatis.logging.stdout.StdOutImpl; +import org.apache.ibatis.scripting.defaults.RawLanguageDriver; +import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSessionFactory; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.ReflectionHints; +import org.springframework.aot.hint.ResourceHints; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; + +/** + * @author Filip Hrisafov + */ +public class FlowableMyBatisRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + // These hints are coming from https://github.com/mybatis/spring-boot-starter/wiki/MyBatisNativeConfiguration.java + MemberCategory[] memberCategories = MemberCategory.values(); + ReflectionHints reflectionHints = hints.reflection(); + reflectionHints.registerType(Configuration.class, memberCategories); + reflectionHints.registerType(RawLanguageDriver.class, memberCategories); + reflectionHints.registerType(XMLLanguageDriver.class, memberCategories); + reflectionHints.registerType(RuntimeSupport.class, memberCategories); + reflectionHints.registerType(ProxyFactory.class, memberCategories); + reflectionHints.registerType(Slf4jImpl.class, memberCategories); + reflectionHints.registerType(Log.class, memberCategories); + reflectionHints.registerType(JakartaCommonsLoggingImpl.class, memberCategories); + reflectionHints.registerType(Log4j2Impl.class, memberCategories); + reflectionHints.registerType(Jdk14LoggingImpl.class, memberCategories); + reflectionHints.registerType(StdOutImpl.class, memberCategories); + reflectionHints.registerType(NoLoggingImpl.class, memberCategories); + reflectionHints.registerType(SqlSessionFactory.class, memberCategories); + reflectionHints.registerType(PerpetualCache.class, memberCategories); + reflectionHints.registerType(FifoCache.class, memberCategories); + reflectionHints.registerType(LruCache.class, memberCategories); + reflectionHints.registerType(SoftCache.class, memberCategories); + reflectionHints.registerType(WeakCache.class, memberCategories); + + ResourceHints resourceHints = hints.resources(); + resourceHints.registerPattern("org/apache/ibatis/builder/xml/*.dtd"); + resourceHints.registerPattern("org/apache/ibatis/builder/xml/*.xsd"); + + } +} diff --git a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableSqlResourceHintsRegistrar.java b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableSqlResourceHintsRegistrar.java new file mode 100644 index 00000000000..9fbf3c0f952 --- /dev/null +++ b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/aot/FlowableSqlResourceHintsRegistrar.java @@ -0,0 +1,31 @@ +/* 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. + */ +package org.flowable.common.engine.impl.aot; + +import java.util.stream.Stream; + +import org.springframework.aot.hint.ResourceHints; + +/** + * Register the necessary resource hints for the Flowable SQL resources. + * + * @author Filip Hrisafov + */ +public class FlowableSqlResourceHintsRegistrar { + + public static void registerSqlResources(String baseFolder, ResourceHints resourceHints) { + Stream.of("create", "drop", "upgrade") + .forEach(folder -> resourceHints.registerPattern(baseFolder + "/" + folder + "/*.sql")); + } + +} diff --git a/modules/flowable-engine-common/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-engine-common/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000000..019e315d926 --- /dev/null +++ b/modules/flowable-engine-common/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,3 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.flowable.common.engine.impl.aot.FlowableCommonRuntimeHints,\ +org.flowable.common.engine.impl.aot.FlowableMyBatisRuntimeHints diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/aot/FlowableProcessRuntimeHints.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/aot/FlowableProcessRuntimeHints.java new file mode 100644 index 00000000000..3c3f730eedc --- /dev/null +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/aot/FlowableProcessRuntimeHints.java @@ -0,0 +1,39 @@ +/* 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. + */ +package org.flowable.engine.impl.aot; + +import org.flowable.common.engine.impl.aot.FlowableMyBatisResourceHintsRegistrar; +import org.flowable.common.engine.impl.aot.FlowableSqlResourceHintsRegistrar; +import org.flowable.variable.service.impl.QueryVariableValue; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.ResourceHints; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; + +/** + * @author Filip Hrisafov + */ +public class FlowableProcessRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + ResourceHints resourceHints = hints.resources(); + resourceHints.registerPattern("META-INF/services/javax.script.ScriptEngineFactory"); + FlowableSqlResourceHintsRegistrar.registerSqlResources("org/flowable/db", resourceHints); + FlowableMyBatisResourceHintsRegistrar.registerMappingResources("org/flowable/db/mapping", hints, classLoader); + resourceHints.registerPattern("org/flowable/impl/bpmn/parser/*.xsd"); + + hints.reflection() + .registerType(QueryVariableValue.class, MemberCategory.INVOKE_PUBLIC_METHODS); + } +} diff --git a/modules/flowable-engine/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-engine/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000000..280667c77f0 --- /dev/null +++ b/modules/flowable-engine/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.flowable.engine.impl.aot.FlowableProcessRuntimeHints diff --git a/modules/flowable-entitylink-service/src/main/java/org/flowable/entitylink/service/impl/aot/FlowableEntityLinkServiceRuntimeHints.java b/modules/flowable-entitylink-service/src/main/java/org/flowable/entitylink/service/impl/aot/FlowableEntityLinkServiceRuntimeHints.java new file mode 100644 index 00000000000..e2554e29758 --- /dev/null +++ b/modules/flowable-entitylink-service/src/main/java/org/flowable/entitylink/service/impl/aot/FlowableEntityLinkServiceRuntimeHints.java @@ -0,0 +1,31 @@ +/* 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. + */ +package org.flowable.entitylink.service.impl.aot; + +import org.flowable.common.engine.impl.aot.FlowableSqlResourceHintsRegistrar; +import org.springframework.aot.hint.ResourceHints; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; + +/** + * @author Filip Hrisafov + */ +public class FlowableEntityLinkServiceRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + ResourceHints resourceHints = hints.resources(); + FlowableSqlResourceHintsRegistrar.registerSqlResources("org/flowable/entitylink/service/db", resourceHints); + + } +} diff --git a/modules/flowable-entitylink-service/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-entitylink-service/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000000..05444f89a41 --- /dev/null +++ b/modules/flowable-entitylink-service/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.flowable.entitylink.service.impl.aot.FlowableEntityLinkServiceRuntimeHints diff --git a/modules/flowable-event-registry/src/main/java/org/flowable/eventregistry/impl/aot/FlowableEventRegistryRuntimeHints.java b/modules/flowable-event-registry/src/main/java/org/flowable/eventregistry/impl/aot/FlowableEventRegistryRuntimeHints.java new file mode 100644 index 00000000000..772beee6752 --- /dev/null +++ b/modules/flowable-event-registry/src/main/java/org/flowable/eventregistry/impl/aot/FlowableEventRegistryRuntimeHints.java @@ -0,0 +1,41 @@ +/* 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. + */ +package org.flowable.eventregistry.impl.aot; + +import java.util.Collections; + +import org.flowable.common.engine.impl.aot.FlowableMyBatisResourceHintsRegistrar; +import org.flowable.eventregistry.impl.db.SetChannelDefinitionTypeAndImplementationCustomChange; +import org.flowable.eventregistry.impl.persistence.ResourceRefTypeHandler; +import org.springframework.aot.hint.ExecutableMode; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.ResourceHints; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; + +/** + * @author Filip Hrisafov + */ +public class FlowableEventRegistryRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + ResourceHints resourceHints = hints.resources(); + FlowableMyBatisResourceHintsRegistrar.registerMappingResources("org/flowable/eventregistry/db/mapping", hints, classLoader); + resourceHints.registerPattern("org/flowable/eventregistry/db/liquibase/flowable-eventregistry-db-changelog.xml"); + hints.reflection() + .registerType(SetChannelDefinitionTypeAndImplementationCustomChange.class, + hint -> hint.withConstructor(Collections.emptyList(), ExecutableMode.INVOKE)) + .registerType(ResourceRefTypeHandler.class, MemberCategory.values()); + } +} diff --git a/modules/flowable-event-registry/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-event-registry/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000000..086d3dfa29e --- /dev/null +++ b/modules/flowable-event-registry/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.flowable.eventregistry.impl.aot.FlowableEventRegistryRuntimeHints diff --git a/modules/flowable-eventsubscription-service/src/main/java/org/flowable/eventsubscription/service/impl/aot/FlowableEventSubscriptionServiceRuntimeHints.java b/modules/flowable-eventsubscription-service/src/main/java/org/flowable/eventsubscription/service/impl/aot/FlowableEventSubscriptionServiceRuntimeHints.java new file mode 100644 index 00000000000..10626b2e586 --- /dev/null +++ b/modules/flowable-eventsubscription-service/src/main/java/org/flowable/eventsubscription/service/impl/aot/FlowableEventSubscriptionServiceRuntimeHints.java @@ -0,0 +1,30 @@ +/* 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. + */ +package org.flowable.eventsubscription.service.impl.aot; + +import org.flowable.common.engine.impl.aot.FlowableSqlResourceHintsRegistrar; +import org.springframework.aot.hint.ResourceHints; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; + +/** + * @author Filip Hrisafov + */ +public class FlowableEventSubscriptionServiceRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + ResourceHints resourceHints = hints.resources(); + FlowableSqlResourceHintsRegistrar.registerSqlResources("org/flowable/eventsubscription/service/db", resourceHints); + } +} diff --git a/modules/flowable-eventsubscription-service/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-eventsubscription-service/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000000..34decbb0ccf --- /dev/null +++ b/modules/flowable-eventsubscription-service/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,3 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.flowable.eventsubscription.service.impl.aot.FlowableEventSubscriptionServiceRuntimeHints + diff --git a/modules/flowable-identitylink-service/src/main/java/org/flowable/identitylink/service/impl/aot/FlowableIdentityLinkServiceRuntimeHints.java b/modules/flowable-identitylink-service/src/main/java/org/flowable/identitylink/service/impl/aot/FlowableIdentityLinkServiceRuntimeHints.java new file mode 100644 index 00000000000..3c33e7770a4 --- /dev/null +++ b/modules/flowable-identitylink-service/src/main/java/org/flowable/identitylink/service/impl/aot/FlowableIdentityLinkServiceRuntimeHints.java @@ -0,0 +1,30 @@ +/* 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. + */ +package org.flowable.identitylink.service.impl.aot; + +import org.flowable.common.engine.impl.aot.FlowableSqlResourceHintsRegistrar; +import org.springframework.aot.hint.ResourceHints; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; + +/** + * @author Filip Hrisafov + */ +public class FlowableIdentityLinkServiceRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + ResourceHints resourceHints = hints.resources(); + FlowableSqlResourceHintsRegistrar.registerSqlResources("org/flowable/identitylink/service/db", resourceHints); + } +} diff --git a/modules/flowable-identitylink-service/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-identitylink-service/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000000..e4a1a9f2cea --- /dev/null +++ b/modules/flowable-identitylink-service/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.flowable.identitylink.service.impl.aot.FlowableIdentityLinkServiceRuntimeHints diff --git a/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/aot/FlowableJobServiceRuntimeHints.java b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/aot/FlowableJobServiceRuntimeHints.java new file mode 100644 index 00000000000..5205999a340 --- /dev/null +++ b/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/aot/FlowableJobServiceRuntimeHints.java @@ -0,0 +1,30 @@ +/* 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. + */ +package org.flowable.job.service.impl.aot; + +import org.flowable.common.engine.impl.aot.FlowableSqlResourceHintsRegistrar; +import org.springframework.aot.hint.ResourceHints; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; + +/** + * @author Filip Hrisafov + */ +public class FlowableJobServiceRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + ResourceHints resourceHints = hints.resources(); + FlowableSqlResourceHintsRegistrar.registerSqlResources("org/flowable/job/service/db", resourceHints); + } +} diff --git a/modules/flowable-job-service/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-job-service/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000000..df8363ddf25 --- /dev/null +++ b/modules/flowable-job-service/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.flowable.job.service.impl.aot.FlowableJobServiceRuntimeHints diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/native.sh b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/native.sh index 29a85ee759a..c4b829695e9 100755 --- a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/native.sh +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/native.sh @@ -1,4 +1,4 @@ #!/usr/bin/env bash rm -rf target mvn -DskipTests -Pnative native:compile -./target/flowable +./target/flowable-spring-boot-sample-native diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/pom.xml b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/pom.xml index cbaa70290d7..a8ac3de9e60 100644 --- a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/pom.xml +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/pom.xml @@ -15,9 +15,12 @@ org.flowable flowable-spring-boot-starter + - org.springframework.boot - spring-boot-starter-data-jpa + jakarta.persistence + jakarta.persistence-api org.springframework.boot @@ -33,6 +36,8 @@ --enable-url-protocols=https + --report-unsupported-elements-at-runtime + -H:+BuildReport @@ -49,4 +54,116 @@ + + + + native + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + paketobuildpacks/builder:tiny + + true + + + + + + process-aot + + process-aot + + + + + + org.graalvm.buildtools + native-maven-plugin + + ${project.build.outputDirectory} + + true + + 22.3 + + + + add-reachability-metadata + + add-reachability-metadata + + + + + + + + + + nativeTest + + + org.junit.platform + junit-platform-launcher + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + process-test-aot + + process-test-aot + + + + + + org.graalvm.buildtools + native-maven-plugin + + ${project.build.outputDirectory} + + true + + 22.3 + + + + native-test + + test + + + + + + + + + diff --git a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/application.properties b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/application.properties index 2740ffc144a..4ff7f396348 100644 --- a/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/application.properties +++ b/modules/flowable-spring-boot/flowable-spring-boot-samples/flowable-spring-boot-sample-native/src/main/resources/application.properties @@ -4,6 +4,5 @@ logging.level.root=INFO spring.sql.init.mode=always -flowable.eventregistry.enabled=false flowable.idm.enabled=false flowable.jpa-enabled=false diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableBeanFactoryInitializationAotProcessor.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableBeanFactoryInitializationAotProcessor.java deleted file mode 100644 index 1316c0ee618..00000000000 --- a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableBeanFactoryInitializationAotProcessor.java +++ /dev/null @@ -1,304 +0,0 @@ -/* 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. - */ -package org.flowable.spring.aot; - -import java.io.IOException; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.apache.ibatis.javassist.util.proxy.ProxyFactory; -import org.apache.ibatis.scripting.defaults.RawLanguageDriver; -import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver; -import org.apache.ibatis.type.TypeHandler; -import org.flowable.bpmn.converter.BpmnXMLConverter; -import org.flowable.bpmn.model.ImplementationType; -import org.flowable.bpmn.model.ServiceTask; -import org.flowable.cmmn.converter.CmmnXmlConverter; -import org.flowable.common.engine.api.query.Query; -import org.flowable.common.engine.impl.db.ListQueryParameterObject; -import org.flowable.common.engine.impl.de.odysseus.el.ExpressionFactoryImpl; -import org.flowable.common.engine.impl.persistence.cache.EntityCacheImpl; -import org.flowable.common.engine.impl.persistence.entity.ByteArrayRef; -import org.flowable.common.engine.impl.persistence.entity.Entity; -import org.flowable.common.engine.impl.persistence.entity.EntityManager; -import org.flowable.common.engine.impl.persistence.entity.TablePageQueryImpl; -import org.flowable.eventregistry.impl.db.SetChannelDefinitionTypeAndImplementationCustomChange; -import org.flowable.variable.api.types.VariableType; -import org.flowable.variable.service.impl.InternalVariableInstanceQueryImpl; -import org.flowable.variable.service.impl.QueryVariableValue; -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.TypeReference; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.boot.autoconfigure.AutoConfigurationPackages; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.core.io.support.ResourcePatternResolver; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * @author Josh Long - * @author Joram Barrez - */ -class FlowableBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor { - - private final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); - - private final Logger log = LoggerFactory.getLogger(getClass()); - - FlowableBeanFactoryInitializationAotProcessor() { - } - - - private Set processResources() { - return resources("processes/**/*.bpmn20.xml", "processes/**/*.bpmn20"); - } - - private Set caseResources() { - return resources("cases/**/*.cmmn", "cases/**/*.cmmn.xml"); - } - - private Set ruleResources() { - return resources("dmn/**/*.dmn", "cases/**/*.dmn.xml"); - } - - private Set flowablePersistenceResources() throws Exception { - - var resources = new HashSet(); - resources.addAll(resources("org/flowable/**/*.sql", "org/flowable/**/*.xml", "org/flowable/**/*.txt", "org/flowable/**/*.xsd", "org/flowable/**/*.properties")); - - resources.addAll(processResources()); - resources.addAll(caseResources()); - resources.addAll(ruleResources()); - - for (var e : "xml,yaml,yml".split(",")) - resources.add(new ClassPathResource("flowable-default." + e)); - - resources.addAll(from(this.resolver.getResources("META-INF/services/org.flowable.common.engine.impl.EngineConfigurator"))); - resources.addAll(from(this.resolver.getResources("org/flowable/common/engine/impl/de/odysseus/el/misc/LocalStrings"))); - return resources.stream() - .filter(Resource::exists) - .collect(Collectors.toSet()); - } - - @Override - public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { - return (generationContext, beanFactoryInitializationCode) -> { - var hints = generationContext.getRuntimeHints(); - beanFactory.getBeanClassLoader(); - try { - - var memberCategories = MemberCategory.values(); - - - for (var c : Set.of(ProxyFactory.class, XMLLanguageDriver.class, - org.apache.ibatis.logging.slf4j.Slf4jImpl.class, EntityCacheImpl.class, - RawLanguageDriver.class, org.apache.ibatis.session.Configuration.class, HashSet.class)) - hints.reflection().registerType(c, memberCategories); - - var types = new Class[]{ - TypeHandler.class, - EntityManager.class, - Entity.class, - Query.class, - VariableType.class, - ListQueryParameterObject.class, - TablePageQueryImpl.class, - SetChannelDefinitionTypeAndImplementationCustomChange.class, - ByteArrayRef.class, - InternalVariableInstanceQueryImpl.class, - QueryVariableValue.class, - ExpressionFactoryImpl.class - }; - - var packagesSet = new HashSet(); - packagesSet.add("org.apache.ibatis"); - packagesSet.add("org.flowable"); - packagesSet.addAll(AutoConfigurationPackages.get(beanFactory)); - var packages = packagesSet.toArray(new String[0]); - - for (var t : types) { - hints.reflection().registerType(t, memberCategories); - var subTypes = FlowableSpringAotUtils.getSubTypesOf(t, packages); - for (var s : subTypes) { - if (StringUtils.hasText(s)) { - hints.reflection().registerType(TypeReference.of(s), memberCategories); - } - } - } - - var resources = new HashSet(); - resources.addAll(flowablePersistenceResources()); - resources.addAll(""" - flowable-default.properties - flowable-default.xml - flowable-default.yaml - flowable-default.yml - """ - .stripIndent() - .stripLeading() - .trim() - .lines() - .map(l -> l.strip().trim()) - .filter(l -> !l.isEmpty()) - .map(ClassPathResource::new) - .toList()); - - - for (var resource : resources) { - if (resource.exists()) { - hints.resources().registerResource(resource); - } - } - - - // here lay dragons; we're going to attempt to proactively register aot hints for beans referenced within definitions - registerAotHintsForReferencedBeans(beanFactory, hints); - - }// - catch (Throwable throwable) { - throw new RuntimeException(throwable); - } - }; - } - - private void registerAotHintsForReferencedBeans(ConfigurableListableBeanFactory beanFactory, RuntimeHints hints) throws IOException { - registerAotHintForProcessDefinitionBeans(beanFactory, hints); - registerAotHintForCaseDefinitionBeans(beanFactory, hints); - } - - protected void registerAotHintForProcessDefinitionBeans(ConfigurableListableBeanFactory beanFactory, RuntimeHints hints) throws IOException { - var processDefinitions = this.processResources(); - for (var processDefinitionXmlResource : processDefinitions) { - Assert.state(processDefinitionXmlResource.exists(), "the process definition file [" + processDefinitionXmlResource.getFilename() + - "] does not exist"); - - hints.resources().registerResource(processDefinitionXmlResource); - try (var in = processDefinitionXmlResource.getInputStream()) { - - var bpmnXMLConverter = new BpmnXMLConverter(); - var bpmnModel = bpmnXMLConverter.convertToBpmnModel(() -> in, false, false); - var serviceTasks = bpmnModel.getMainProcess().findFlowElementsOfType(ServiceTask.class); - for (var serviceTask : serviceTasks) { - if (serviceTask.getImplementationType().equals(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION)) { - var expression = serviceTask.getImplementation(); - var expressionWithoutDelimiters = expression.substring(2); - expressionWithoutDelimiters = expressionWithoutDelimiters.substring(0, expressionWithoutDelimiters.length() - 1); - var beanName = expressionWithoutDelimiters; - try { - var beanDefinition = beanFactory.getBeanDefinition(beanName); - hints.reflection().registerType(TypeReference.of(beanDefinition.getBeanClassName()), MemberCategory.values()); - - log.debug("registering hint for bean name [" + beanName + "]"); - } - catch (Throwable throwable) { - log.error("couldn't find bean named [" + beanName + "]"); - } - - } - } - } - } - } - - protected void registerAotHintForCaseDefinitionBeans(ConfigurableListableBeanFactory beanFactory, RuntimeHints hints) throws IOException { - var caseDefinitions = this.caseResources(); - for (var caseDefinitionXmlResource : caseDefinitions) { - Assert.state(caseDefinitionXmlResource.exists(), "the case definition file [" + caseDefinitionXmlResource.getFilename() + - "] does not exist"); - - hints.resources().registerResource(caseDefinitionXmlResource); - try (var in = caseDefinitionXmlResource.getInputStream()) { - - var cmmXmlConverter = new CmmnXmlConverter(); - var cmmnModel = cmmXmlConverter.convertToCmmnModel(() -> in, false, false); - var serviceTasks = cmmnModel.getPrimaryCase().findPlanItemDefinitionsOfType(org.flowable.cmmn.model.ServiceTask.class); - for (var serviceTask : serviceTasks) { - if (serviceTask.getImplementationType().equals(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION)) { - var expression = serviceTask.getImplementation(); - var expressionWithoutDelimiters = expression.substring(2); - expressionWithoutDelimiters = expressionWithoutDelimiters.substring(0, expressionWithoutDelimiters.length() - 1); - var beanName = expressionWithoutDelimiters; - try { - var beanDefinition = beanFactory.getBeanDefinition(beanName); - hints.reflection().registerType(TypeReference.of(beanDefinition.getBeanClassName()), MemberCategory.values()); - - log.debug("registering hint for bean name [" + beanName + "]"); - } - catch (Throwable throwable) { - log.error("couldn't find bean named [" + beanName + "]"); - } - - } - } - } - } - } - - private static Set from(T[] t) { - return new HashSet<>(Arrays.asList(t)); - } - - private static Resource newResourceFor(Resource in) { - try { - var marker = "jar!"; - var externalFormOfUrl = in.getURL().toExternalForm(); - if (externalFormOfUrl.contains(marker)) { - var rest = externalFormOfUrl.substring(externalFormOfUrl.lastIndexOf(marker) + marker.length()); - return new ClassPathResource(rest); - }// - else { - // ugh i think this only works for maven? what about gradle? - var classesSubstring = "classes/"; - var locationOfClassesInUrl = externalFormOfUrl.indexOf(classesSubstring); - if (locationOfClassesInUrl != -1) { - return new ClassPathResource(externalFormOfUrl.substring(locationOfClassesInUrl + classesSubstring.length())); - } - - } - - return in; - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - - Set resources(String... patterns) { - return Stream - .of(patterns) - .map(path -> ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + path) - .flatMap(p -> { - try { - return Stream.of(this.resolver.getResources(p)); - }// - catch (IOException e) { - throw new RuntimeException(e); - } - }) - .map(FlowableBeanFactoryInitializationAotProcessor::newResourceFor) - .filter(Resource::exists) - .collect(Collectors.toSet()); - } - - -} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableMybatisMappersBeanFactoryInitializationAotProcessor.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableMybatisMappersBeanFactoryInitializationAotProcessor.java deleted file mode 100644 index 668f4e8fac4..00000000000 --- a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableMybatisMappersBeanFactoryInitializationAotProcessor.java +++ /dev/null @@ -1,38 +0,0 @@ -/* 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. - */ -package org.flowable.spring.aot; - -import java.util.ArrayList; -import java.util.List; - -import org.springframework.beans.factory.BeanFactory; - -/** - * @author Josh Long - */ -class FlowableMybatisMappersBeanFactoryInitializationAotProcessor - extends MybatisMappersBeanFactoryInitializationAotProcessor { - - FlowableMybatisMappersBeanFactoryInitializationAotProcessor() { - - } - - @Override - protected List getPackagesToScan(BeanFactory b) { - var defaults = super.getPackagesToScan(b); - var l = new ArrayList(); - l.add("org.flowable"); - l.addAll(defaults); - return l; - } -} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableSpringAotUtils.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableSpringAotUtils.java deleted file mode 100644 index 3c172cc771d..00000000000 --- a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/FlowableSpringAotUtils.java +++ /dev/null @@ -1,85 +0,0 @@ -/* 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. - */ -package org.flowable.spring.aot; - -import java.io.IOException; -import java.io.Serializable; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -import org.springframework.core.type.filter.AssignableTypeFilter; - -/** - * @author Josh Long - */ -final class FlowableSpringAotUtils { - - private static final Logger log = LoggerFactory.getLogger(FlowableSpringAotUtils.class); - - private FlowableSpringAotUtils() { - } - - static Resource newResourceFor(Resource in) { - try { - var marker = "jar!"; - var p = in.getURL().toExternalForm(); - var rest = p.substring(p.lastIndexOf(marker) + marker.length()); - return new ClassPathResource(rest); - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - - static boolean isSerializable(Class clazz) { - return Serializable.class.isAssignableFrom(clazz); - } - - static void debug(String message, Collection tCollection) { - log.debug(message + System.lineSeparator()); - for (var t : tCollection) - log.debug('\t' + t.toString()); - log.debug(System.lineSeparator()); - } - - static String packageToPath(String packageName) { - var sb = new StringBuilder(); - for (var c : packageName.toCharArray()) - sb.append(c == '.' ? '/' : c); - return sb.toString(); - } - - - static Set getSubTypesOf(Class clzzName, String... packages) { - var set = new HashSet(); - - for (var p : packages) { - var classPathScanningCandidateComponentProvider = new ClassPathScanningCandidateComponentProvider(false); - - classPathScanningCandidateComponentProvider.addIncludeFilter(new AssignableTypeFilter(clzzName)); - - var results = classPathScanningCandidateComponentProvider.findCandidateComponents(p); - for (var r : results) - set.add(r.getBeanClassName()); - } - - return set; - - } -} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisApplicationSpecificBeanFactoryInitializationAotProcessor.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisApplicationSpecificBeanFactoryInitializationAotProcessor.java deleted file mode 100644 index 06748d48147..00000000000 --- a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisApplicationSpecificBeanFactoryInitializationAotProcessor.java +++ /dev/null @@ -1,169 +0,0 @@ -/* 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. - */ -package org.flowable.spring.aot; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; -import java.util.function.Function; - -import org.apache.ibatis.annotations.DeleteProvider; -import org.apache.ibatis.annotations.InsertProvider; -import org.apache.ibatis.annotations.SelectProvider; -import org.apache.ibatis.annotations.UpdateProvider; -import org.springframework.aot.hint.MemberCategory; -import org.springframework.aot.hint.TypeReference; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.boot.autoconfigure.AutoConfigurationPackages; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.core.io.support.ResourcePatternResolver; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; - -/** - * Register hints based upon the structure of a particular user's Spring Boot application - * packages and {@link org.springframework.beans.factory.BeanFactory} - * - * @author Josh Long - */ -class MybatisApplicationSpecificBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor { - - private final PathMatchingResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); - - MybatisApplicationSpecificBeanFactoryInitializationAotProcessor() { - } - - private Collection attemptToRegisterXmlResourcesForBasePackage( - ConfigurableListableBeanFactory beanFactory) throws Exception { - var set = new HashSet(); - for (var packageName : AutoConfigurationPackages.get(beanFactory)) { - Assert.hasText(packageName, "the package name must not be empty!"); - var path = FlowableSpringAotUtils.packageToPath(packageName); - for (var resolvedXmlResource : this.resourcePatternResolver - .getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + path + "/**/*.xml")) { - var fqn = resolvedXmlResource.getURI().toString(); - if (resolvedXmlResource.exists()) { - var np = fqn.substring(fqn.indexOf(path)); - var npr = new ClassPathResource(np); - set.add(npr); - } - } - } - return set; - } - - @Override - public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { - - if (!ClassUtils.isPresent("org.mybatis.spring.mapper.MapperFactoryBean", beanFactory.getBeanClassLoader())) - return null; - - try { - var classesToRegister = new HashSet>(); - var proxiesToRegister = new HashSet>(); - var resourcesToRegister = new HashSet(); - resourcesToRegister.addAll(attemptToRegisterXmlResourcesForBasePackage(beanFactory)); - - // Flowable doesn't use mapper classes -// var beanNames = beanFactory.getBeanNamesForType(MapperFactoryBean.class); -// for (var beanName : beanNames) { -// var beanDefinition = beanFactory.getBeanDefinition(beanName.substring(1)); -// var mapperInterface = beanDefinition.getPropertyValues().getPropertyValue("mapperInterface"); -// if (mapperInterface != null && mapperInterface.getValue() != null) { -// var mapperInterfaceType = (Class) mapperInterface.getValue(); -// if (mapperInterfaceType != null) { -// proxiesToRegister.add(mapperInterfaceType); -// resourcesToRegister -// .add(new ClassPathResource(mapperInterfaceType.getName().replace('.', '/').concat(".xml"))); -// registerReflectionTypeIfNecessary(mapperInterfaceType, classesToRegister); -// registerMapperRelationships(mapperInterfaceType, classesToRegister); -// } -// } -// } - - return (generationContext, beanFactoryInitializationCode) -> { - var mcs = MemberCategory.values(); - var runtimeHints = generationContext.getRuntimeHints(); - FlowableSpringAotUtils.debug("proxies", proxiesToRegister); - FlowableSpringAotUtils.debug("classes for reflection", classesToRegister); - FlowableSpringAotUtils.debug("resources", resourcesToRegister); - for (var c : proxiesToRegister) { - runtimeHints.proxies().registerJdkProxy(c); - runtimeHints.reflection().registerType(c, mcs); - } - for (var c : classesToRegister) { - runtimeHints.reflection().registerType(c, mcs); - if (FlowableSpringAotUtils.isSerializable(c)) - runtimeHints.serialization().registerType(TypeReference.of(c.getName())); - } - for (var r : resourcesToRegister) { - if (r.exists()) { - runtimeHints.resources().registerResource(r); - } - } - }; - } // - catch (Exception e) { - throw new RuntimeException(e); - } - } - - @SafeVarargs - private void registerSqlProviderTypes(Method method, Set> registry, - Class annotationType, Function>... providerTypeResolvers) { - for (T annotation : method.getAnnotationsByType(annotationType)) { - for (Function> providerTypeResolver : providerTypeResolvers) { - registerReflectionTypeIfNecessary(providerTypeResolver.apply(annotation), registry); - } - } - } - - private void registerReflectionTypeIfNecessary(Class type, Set> registry) { - if (!type.isPrimitive() && !type.getName().startsWith("java")) { - registry.add(type); - } - } - - private void registerMapperRelationships(Class mapperInterfaceType, Set> registry) { - var methods = ReflectionUtils.getAllDeclaredMethods(mapperInterfaceType); - for (var method : methods) { - if (method.getDeclaringClass() != Object.class) { - - ReflectionUtils.makeAccessible(method); - - registerSqlProviderTypes(method, registry, SelectProvider.class, SelectProvider::value, - SelectProvider::type); - registerSqlProviderTypes(method, registry, InsertProvider.class, InsertProvider::value, - InsertProvider::type); - registerSqlProviderTypes(method, registry, UpdateProvider.class, UpdateProvider::value, - UpdateProvider::type); - registerSqlProviderTypes(method, registry, DeleteProvider.class, DeleteProvider::value, - DeleteProvider::type); - - var returnType = MybatisMapperTypeUtils.resolveReturnClass(mapperInterfaceType, method); - registerReflectionTypeIfNecessary(returnType, registry); - - MybatisMapperTypeUtils.resolveParameterClasses(mapperInterfaceType, method) - .forEach(x -> registerReflectionTypeIfNecessary(x, registry)); - } - } - } - -} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisGlobalBeanFactoryInitializationAotProcessor.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisGlobalBeanFactoryInitializationAotProcessor.java deleted file mode 100644 index af8d9ea4a2f..00000000000 --- a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisGlobalBeanFactoryInitializationAotProcessor.java +++ /dev/null @@ -1,262 +0,0 @@ -/* 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. - */ -package org.flowable.spring.aot; - -import java.io.IOException; -import java.io.Serializable; -import java.lang.invoke.MethodHandles; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.TypeVariable; -import java.lang.reflect.WildcardType; -import java.sql.CallableStatement; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.util.AbstractList; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.RandomAccess; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.stream.Stream; - -import org.apache.ibatis.annotations.CacheNamespace; -import org.apache.ibatis.annotations.Delete; -import org.apache.ibatis.annotations.DeleteProvider; -import org.apache.ibatis.annotations.Flush; -import org.apache.ibatis.annotations.Insert; -import org.apache.ibatis.annotations.InsertProvider; -import org.apache.ibatis.annotations.Many; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.One; -import org.apache.ibatis.annotations.Options; -import org.apache.ibatis.annotations.Property; -import org.apache.ibatis.annotations.Result; -import org.apache.ibatis.annotations.Results; -import org.apache.ibatis.annotations.Select; -import org.apache.ibatis.annotations.SelectProvider; -import org.apache.ibatis.annotations.Update; -import org.apache.ibatis.annotations.UpdateProvider; -import org.apache.ibatis.builder.CacheRefResolver; -import org.apache.ibatis.builder.ResultMapResolver; -import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder; -import org.apache.ibatis.builder.annotation.MethodResolver; -import org.apache.ibatis.builder.annotation.ProviderContext; -import org.apache.ibatis.builder.annotation.ProviderMethodResolver; -import org.apache.ibatis.cache.Cache; -import org.apache.ibatis.cache.CacheKey; -import org.apache.ibatis.cache.NullCacheKey; -import org.apache.ibatis.cache.decorators.BlockingCache; -import org.apache.ibatis.cache.decorators.FifoCache; -import org.apache.ibatis.cache.decorators.LoggingCache; -import org.apache.ibatis.cache.decorators.LruCache; -import org.apache.ibatis.cache.decorators.SerializedCache; -import org.apache.ibatis.cache.decorators.SynchronizedCache; -import org.apache.ibatis.cache.decorators.TransactionalCache; -import org.apache.ibatis.cache.decorators.WeakCache; -import org.apache.ibatis.cache.impl.PerpetualCache; -import org.apache.ibatis.cursor.Cursor; -import org.apache.ibatis.javassist.util.proxy.ProxyFactory; -import org.apache.ibatis.javassist.util.proxy.RuntimeSupport; -import org.apache.ibatis.logging.Log; -import org.apache.ibatis.logging.LogFactory; -import org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl; -import org.apache.ibatis.logging.jdbc.BaseJdbcLogger; -import org.apache.ibatis.logging.jdbc.ConnectionLogger; -import org.apache.ibatis.logging.jdbc.PreparedStatementLogger; -import org.apache.ibatis.logging.jdbc.ResultSetLogger; -import org.apache.ibatis.logging.jdbc.StatementLogger; -import org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl; -import org.apache.ibatis.logging.log4j.Log4jImpl; -import org.apache.ibatis.logging.log4j2.Log4j2AbstractLoggerImpl; -import org.apache.ibatis.logging.log4j2.Log4j2Impl; -import org.apache.ibatis.logging.log4j2.Log4j2LoggerImpl; -import org.apache.ibatis.logging.nologging.NoLoggingImpl; -import org.apache.ibatis.logging.slf4j.Slf4jImpl; -import org.apache.ibatis.logging.stdout.StdOutImpl; -import org.apache.ibatis.mapping.ResultFlag; -import org.apache.ibatis.parsing.XNode; -import org.apache.ibatis.scripting.defaults.RawLanguageDriver; -import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver; -import org.apache.ibatis.session.Configuration; -import org.apache.ibatis.session.SqlSession; -import org.apache.ibatis.session.SqlSessionFactory; -import org.apache.logging.slf4j.SLF4JLogger; -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.TypeReference; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.core.io.support.ResourcePatternResolver; - -/** - * Register invariant hints for Mybatis that are presumed the same for all applications - * - * @author Josh Long - */ -class MybatisGlobalBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor { - - private final PathMatchingResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); - - private final Logger log = LoggerFactory.getLogger(getClass()); - - MybatisGlobalBeanFactoryInitializationAotProcessor() { - } - - private void registerProxies(RuntimeHints hints) { - var proxies = Set.of(Set.of(Connection.class.getName()), Set.of(SqlSession.class.getName()), - Set.of(PreparedStatement.class.getName(), CallableStatement.class.getName()), - Set.of(ParameterizedType.class.getName(), - "org.springframework.core.SerializableTypeWrapper$SerializableTypeProxy", - Serializable.class.getName()), - Set.of(TypeVariable.class.getName(), - "org.springframework.core.SerializableTypeWrapper$SerializableTypeProxy", - Serializable.class.getName()), - Set.of(WildcardType.class.getName(), - "org.springframework.core.SerializableTypeWrapper$SerializableTypeProxy", - Serializable.class.getName())); - FlowableSpringAotUtils.debug("global proxies", proxies); - for (var p : proxies) { - var parts = p.stream().map(TypeReference::of).toArray(TypeReference[]::new); - hints.proxies().registerJdkProxy(parts); - } - } - - private static Resource newResourceFor(Resource in) { - try { - var marker = "jar!"; - var p = in.getURL().toExternalForm(); - var rest = p.substring(p.lastIndexOf(marker) + marker.length()); - return new ClassPathResource(rest); - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - - private void registerResources(RuntimeHints hints) throws IOException { - - var resources = new HashSet(); - var config = Stream - .of("org/apache/ibatis/builder/xml/*.dtd", "org/apache/ibatis/builder/xml/*.xsd", - "org/mybatis/spring/config/*.xsd") - .map(p -> ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + p) - .flatMap(p -> { - try { - return Stream.of(this.resourcePatternResolver.getResources(p)); - } - catch (IOException e) { - throw new RuntimeException(e); - } - }) - .map(MybatisGlobalBeanFactoryInitializationAotProcessor::newResourceFor) - .filter(Resource::exists) - .toList(); - - resources.addAll(config); - - FlowableSpringAotUtils.debug("resources", resources); - for (var r : resources) - hints.resources().registerResource(r); - } - - private void registerGlobalTypeHints(RuntimeHints hints) { - - var caches = Set.of(Cache.class, LruCache.class, BlockingCache.class, SerializedCache.class, FifoCache.class, - NullCacheKey.class, PerpetualCache.class, CacheKey.class, WeakCache.class, TransactionalCache.class, - SynchronizedCache.class, LoggingCache.class); - - var collections = Set.of(AbstractList.class, List.class, RandomAccess.class, Cloneable.class, Collection.class, - TreeSet.class, SortedSet.class, Iterator.class, ArrayList.class, HashSet.class, Set.class, Map.class); - - var loggers = Set.of(Log4jImpl.class, Log4j2Impl.class, Log4j2LoggerImpl.class, Log4j2AbstractLoggerImpl.class, - NoLoggingImpl.class, SLF4JLogger.class, StdOutImpl.class, BaseJdbcLogger.class, ConnectionLogger.class, - PreparedStatementLogger.class, ResultSetLogger.class, StatementLogger.class, Jdk14LoggingImpl.class, - JakartaCommonsLoggingImpl.class, Slf4jImpl.class); - - var annotations = Set.of(Select.class, Update.class, Insert.class, Delete.class, SelectProvider.class, - UpdateProvider.class, InsertProvider.class, CacheNamespace.class, Flush.class, DeleteProvider.class, - Options.class, Options.FlushCachePolicy.class, Many.class, Mapper.class, One.class, Property.class, - Result.class, Results.class); - - var memberCategories = MemberCategory.values(); - - var classesForReflection = new HashSet>(); - - classesForReflection.addAll(caches); - classesForReflection.addAll(annotations); - classesForReflection.addAll(loggers); - classesForReflection.addAll(collections); - - // Original version: -// classesForReflection.addAll(Set.of(Serializable.class, SpringBootVFS.class, PerpetualCache.class, Cursor.class, -// Optional.class, LruCache.class, MethodHandles.class, Date.class, HashMap.class, CacheRefResolver.class, -// XNode.class, ResultFlag.class, ResultMapResolver.class, MapperScannerConfigurer.class, -// MethodResolver.class, ProviderMethodResolver.class, ProviderContext.class, -// MapperAnnotationBuilder.class, Logger.class, LogFactory.class, RuntimeSupport.class, Log.class, -// SqlSessionTemplate.class, SqlSessionFactory.class, SqlSessionFactoryBean.class, ProxyFactory.class, -// XMLLanguageDriver.class, RawLanguageDriver.class, Configuration.class, String.class, int.class, -// Number.class, Integer.class, long.class, Long.class, short.class, Short.class, byte.class, Byte.class, -// float.class, Float.class, boolean.class, Boolean.class, double.class, Double.class)); - - classesForReflection.addAll(Set.of(Serializable.class, PerpetualCache.class, Cursor.class, - Optional.class, LruCache.class, MethodHandles.class, Date.class, HashMap.class, CacheRefResolver.class, - XNode.class, ResultFlag.class, ResultMapResolver.class, - MethodResolver.class, ProviderMethodResolver.class, ProviderContext.class, - MapperAnnotationBuilder.class, Logger.class, LogFactory.class, RuntimeSupport.class, Log.class, - SqlSessionFactory.class, ProxyFactory.class, - XMLLanguageDriver.class, RawLanguageDriver.class, Configuration.class, String.class, int.class, - Number.class, Integer.class, long.class, Long.class, short.class, Short.class, byte.class, Byte.class, - float.class, Float.class, boolean.class, Boolean.class, double.class, Double.class)); - - FlowableSpringAotUtils.debug("global types for reflection", classesForReflection); - - for (var c : classesForReflection) { - hints.reflection().registerType(c, memberCategories); - if (FlowableSpringAotUtils.isSerializable(c)) { - hints.serialization().registerType(TypeReference.of(c.getName())); - if (log.isDebugEnabled()) - log.debug("the type " + c.getName() + " is serializable"); - } - } - } - - @Override - public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { - return (generationContext, beanFactoryInitializationCode) -> { - try { - var hints = generationContext.getRuntimeHints(); - registerResources(hints); - registerGlobalTypeHints(hints); - registerProxies(hints); - } // - catch (Throwable throwable) { - throw new RuntimeException(throwable); - } - }; - } - -} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisMapperTypeUtils.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisMapperTypeUtils.java deleted file mode 100644 index 89d6e7dec95..00000000000 --- a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisMapperTypeUtils.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2022 the original author or authors. - * - * 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 - * - * https://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.flowable.spring.aot; - -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.apache.ibatis.reflection.TypeParameterResolver; - -/** - * @author Josh Long - */ -final class MybatisMapperTypeUtils { - - - static Class resolveReturnClass(Class mapperInterface, Method method) { - var resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface); - return typeToClass(resolvedReturnType, method.getReturnType()); - } - - static Set> resolveParameterClasses(Class mapperInterface, Method method) { - return Stream.of(TypeParameterResolver.resolveParamTypes(method, mapperInterface)) - .map(x -> typeToClass(x, x instanceof Class ? (Class) x : Object.class)) - .collect(Collectors.toSet()); - } - - private static Class typeToClass(Type src, Class fallback) { - var result = (Class) null; - if (src instanceof Class c) { - result = c.isArray() ? c.getComponentType() : c; - } - else if (src instanceof ParameterizedType parameterizedType) { - var index = (parameterizedType.getRawType() instanceof Class - && Map.class.isAssignableFrom((Class) parameterizedType.getRawType()) - && parameterizedType.getActualTypeArguments().length > 1) ? 1 : 0; - var actualType = parameterizedType.getActualTypeArguments()[index]; - result = typeToClass(actualType, fallback); - } - if (result == null) { - result = fallback; - } - return result; - } - -} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisMappersBeanFactoryInitializationAotProcessor.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisMappersBeanFactoryInitializationAotProcessor.java deleted file mode 100644 index eb626b8ae57..00000000000 --- a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/aot/MybatisMappersBeanFactoryInitializationAotProcessor.java +++ /dev/null @@ -1,133 +0,0 @@ -/* 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. - */ -package org.flowable.spring.aot; - -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.StringReader; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import javax.xml.parsers.DocumentBuilderFactory; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.boot.autoconfigure.AutoConfigurationPackages; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.core.io.support.ResourcePatternResolver; -import org.springframework.util.FileCopyUtils; -import org.w3c.dom.Element; -import org.xml.sax.InputSource; - -/** - * Discovers any {@literal mappings.xml} and reads them in to then register the - * referenced {@literal .xml} files as resource hints. - * - * @author Josh Long - */ -class MybatisMappersBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor { - - private final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); - - MybatisMappersBeanFactoryInitializationAotProcessor() { - } - - private Set persistenceResources(String rootPackage) throws Exception { - var folderFromPackage = FlowableSpringAotUtils.packageToPath(rootPackage); - var patterns = Stream// - .of(folderFromPackage + "/**/mappings.xml")// - .map(path -> ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + path)// - .flatMap(p -> { - try { - return Stream.of(this.resolver.getResources(p)); - } // - catch (IOException e) { - throw new RuntimeException(e); - } - })// - .map(FlowableSpringAotUtils::newResourceFor) - .toList(); - - var resources = new HashSet(); - for (var p : patterns) { - var mappers = mappers(p); - resources.add(p); - resources.addAll(mappers); - } - return resources.stream().filter(Resource::exists).collect(Collectors.toSet()); - } - - protected List getPackagesToScan (BeanFactory b){ - return AutoConfigurationPackages.get(b) ; - } - - @Override - public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { - try { - var packages = getPackagesToScan(beanFactory); - var resources = new HashSet(); - for (var pkg : packages) { - resources.addAll(persistenceResources(pkg)); - } - return (generationContext, beanFactoryInitializationCode) -> { - for (var r : resources) - if (r.exists()) - generationContext.getRuntimeHints().resources().registerResource(r); - }; - } - catch (Exception e) { - throw new RuntimeException(e); - } - } - - private Set mappers(Resource mapping) throws Exception { - var resources = new HashSet(); - try (var in = new InputStreamReader(mapping.getInputStream())) { - var xml = FileCopyUtils.copyToString(in); - resources.addAll(mapperResources(xml)); - } - resources.add(mapping); - return resources; - - } - - private Set mapperResources(String xml) { - try { - var set = new HashSet(); - var dbf = DocumentBuilderFactory.newInstance(); - var db = dbf.newDocumentBuilder(); - var is = new InputSource(new StringReader(xml)); - var doc = db.parse(is); - var mappersElement = (Element) doc.getElementsByTagName("mappers").item(0); - var mapperList = mappersElement.getElementsByTagName("mapper"); - for (var i = 0; i < mapperList.getLength(); i++) { - var mapperElement = (Element) mapperList.item(i); - var resourceValue = mapperElement.getAttribute("resource"); - set.add(new ClassPathResource(resourceValue)); - } - return set; - } // - catch (Throwable throwable) { - throw new RuntimeException(throwable); - } - - } - -} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/BaseAutoDeployResourceContribution.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/BaseAutoDeployResourceContribution.java new file mode 100644 index 00000000000..b67b5a620d3 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/BaseAutoDeployResourceContribution.java @@ -0,0 +1,100 @@ +/* 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. + */ +package org.flowable.spring.boot.aot; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Collection; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aot.generate.GenerationContext; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; + +/** + * @author Josh Long + * @author Joram Barrez + * @author Filip Hrisafov + */ +public class BaseAutoDeployResourceContribution implements BeanFactoryInitializationAotContribution { + + protected final ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + protected final Logger logger = LoggerFactory.getLogger(getClass()); + protected final String locationPrefix; + protected final Collection locationSuffixes; + + public BaseAutoDeployResourceContribution(String locationPrefix, Collection locationSuffixes) { + this.locationPrefix = locationPrefix; + this.locationSuffixes = locationSuffixes; + } + + @Override + public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) { + RuntimeHints runtimeHints = generationContext.getRuntimeHints(); + for (String locationSuffix : locationSuffixes) { + String path = locationPrefix + locationSuffix; + try { + for (Resource resource : resolver.getResources(path)) { + ClassPathResource classPathResource = asClasspathResource(resource); + if (classPathResource != null && classPathResource.exists()) { + logger.debug("Registering hints for {}", classPathResource); + applyToResource(classPathResource, runtimeHints); + } + } + } catch (IOException e) { + throw new UncheckedIOException("Failed to find resources for " + path, e); + } + + } + } + + protected void applyToResource(ClassPathResource resource, RuntimeHints hints) { + hints.resources().registerResource(resource); + } + + protected ClassPathResource asClasspathResource(Resource resource) { + if (resource instanceof ClassPathResource) { + return (ClassPathResource) resource; + } + try { + logger.debug("Transforming {} to a classpath resource", resource); + String marker = "jar!"; + String externalFormOfUrl = resource.getURL().toExternalForm(); + if (externalFormOfUrl.contains(marker)) { + String rest = externalFormOfUrl.substring(externalFormOfUrl.lastIndexOf(marker) + marker.length()); + return new ClassPathResource(rest); + } else { + // ugh i think this only works for maven? what about gradle? + var classesSubstring = "classes/"; + var locationOfClassesInUrl = externalFormOfUrl.indexOf(classesSubstring); + if (locationOfClassesInUrl != -1) { + return new ClassPathResource(externalFormOfUrl.substring(locationOfClassesInUrl + classesSubstring.length())); + } + + } + + logger.error("Could not resolve {} as a classpath resource", resource); + return null; + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/cmmn/FlowableCmmnAutoDeployBeanFactoryInitializationAotProcessor.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/cmmn/FlowableCmmnAutoDeployBeanFactoryInitializationAotProcessor.java new file mode 100644 index 00000000000..10a29cd0695 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/cmmn/FlowableCmmnAutoDeployBeanFactoryInitializationAotProcessor.java @@ -0,0 +1,122 @@ +/* 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. + */ +package org.flowable.spring.boot.aot.cmmn; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Collection; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.flowable.cmmn.converter.CmmnXmlConverter; +import org.flowable.cmmn.model.CmmnModel; +import org.flowable.cmmn.model.ImplementationType; +import org.flowable.cmmn.model.ServiceTask; +import org.flowable.spring.boot.aot.BaseAutoDeployResourceContribution; +import org.flowable.spring.boot.cmmn.FlowableCmmnProperties; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.TypeReference; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.util.ClassUtils; +import org.springframework.util.ResourceUtils; + +/** + * @author Josh Long + * @author Joram Barrez + * @author Filip Hrisafov + */ +public class FlowableCmmnAutoDeployBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor { + + protected final ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + + @Override + public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { + if (!ClassUtils.isPresent("org.flowable.cmmn.spring.SpringCmmnEngineConfiguration", beanFactory.getBeanClassLoader())) { + return null; + } + + if (!beanFactory.containsBean("cmmnEngineConfiguration")) { + return null; + } + + FlowableCmmnProperties properties = beanFactory.getBeanProvider(FlowableCmmnProperties.class) + .getIfAvailable(); + if (properties == null || !properties.isDeployResources()) { + return null; + } + List locationSuffixes = properties.getResourceSuffixes(); + if (locationSuffixes.isEmpty()) { + return null; + } + String locationPrefix = properties.getResourceLocation(); + if (locationPrefix.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX) || locationPrefix.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX)) { + return new CmmnAutoDeployResourceContribution(locationPrefix, locationSuffixes, beanFactory); + } + + return null; + } + + static class CmmnAutoDeployResourceContribution extends BaseAutoDeployResourceContribution { + + protected final ConfigurableListableBeanFactory beanFactory; + + public CmmnAutoDeployResourceContribution(String locationPrefix, Collection locationSuffixes, ConfigurableListableBeanFactory beanFactory) { + super(locationPrefix, locationSuffixes); + this.beanFactory = beanFactory; + } + + @Override + protected void applyToResource(ClassPathResource resource, RuntimeHints hints) { + super.applyToResource(resource, hints); + CmmnXmlConverter cmmXmlConverter = new CmmnXmlConverter(); + CmmnModel cmmnModel = cmmXmlConverter.convertToCmmnModel(() -> { + try { + return resource.getInputStream(); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read resource " + resource, e); + } + }, false, false); + Collection serviceTasks = cmmnModel.getPrimaryCase().findPlanItemDefinitionsOfType(ServiceTask.class); + for (ServiceTask serviceTask : serviceTasks) { + if (ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION.equals(serviceTask.getImplementationType())) { + String expression = serviceTask.getImplementation(); + String expressionWithoutDelimiters = expression.substring(2); + expressionWithoutDelimiters = expressionWithoutDelimiters.substring(0, expressionWithoutDelimiters.length() - 1); + String beanName = expressionWithoutDelimiters; + try { + BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); + String beanClassName = beanDefinition.getBeanClassName(); + if (StringUtils.isNotEmpty(beanClassName)) { + hints.reflection().registerType(TypeReference.of(beanClassName), MemberCategory.values()); + logger.debug("Registering hint for bean name [{}] for service task {} in {}", beanName, serviceTask.getId(), resource); + } else { + logger.debug("No bean class name for bean name [{}] for service task {} in {}", beanName, serviceTask.getId(), resource); + } + + } catch (Throwable throwable) { + logger.error("Couldn't find bean named [{}] for service task {} in {}", beanName, serviceTask.getId(), resource); + } + + } + } + + } + } +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/dmn/FlowableDmnAutoDeployBeanFactoryInitializationAotProcessor.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/dmn/FlowableDmnAutoDeployBeanFactoryInitializationAotProcessor.java new file mode 100644 index 00000000000..b7c4744d527 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/dmn/FlowableDmnAutoDeployBeanFactoryInitializationAotProcessor.java @@ -0,0 +1,63 @@ +/* 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. + */ +package org.flowable.spring.boot.aot.dmn; + +import java.util.List; + +import org.flowable.spring.boot.aot.BaseAutoDeployResourceContribution; +import org.flowable.spring.boot.dmn.FlowableDmnProperties; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.util.ClassUtils; +import org.springframework.util.ResourceUtils; + +/** + * @author Josh Long + * @author Joram Barrez + * @author Filip Hrisafov + */ +public class FlowableDmnAutoDeployBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor { + + protected final ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + + @Override + public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { + if (!ClassUtils.isPresent("org.flowable.dmn.spring.SpringDmnEngineConfiguration", beanFactory.getBeanClassLoader())) { + return null; + } + + if (!beanFactory.containsBean("dmnEngineConfiguration")) { + return null; + } + + FlowableDmnProperties properties = beanFactory.getBeanProvider(FlowableDmnProperties.class) + .getIfAvailable(); + if (properties == null || !properties.isDeployResources()) { + return null; + } + List locationSuffixes = properties.getResourceSuffixes(); + if (locationSuffixes.isEmpty()) { + return null; + } + String locationPrefix = properties.getResourceLocation(); + if (locationPrefix.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX) || locationPrefix.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX)) { + return new BaseAutoDeployResourceContribution(locationPrefix, locationSuffixes); + } + + return null; + } + +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/process/FlowableProcessAutoDeployBeanFactoryInitializationAotProcessor.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/process/FlowableProcessAutoDeployBeanFactoryInitializationAotProcessor.java new file mode 100644 index 00000000000..647368db3b5 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/aot/process/FlowableProcessAutoDeployBeanFactoryInitializationAotProcessor.java @@ -0,0 +1,122 @@ +/* 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. + */ +package org.flowable.spring.boot.aot.process; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Collection; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.flowable.bpmn.converter.BpmnXMLConverter; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.ImplementationType; +import org.flowable.bpmn.model.ServiceTask; +import org.flowable.spring.boot.FlowableProperties; +import org.flowable.spring.boot.aot.BaseAutoDeployResourceContribution; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.TypeReference; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.util.ClassUtils; +import org.springframework.util.ResourceUtils; + +/** + * @author Josh Long + * @author Joram Barrez + * @author Filip Hrisafov + */ +public class FlowableProcessAutoDeployBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor { + + protected final ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + + @Override + public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { + if (!ClassUtils.isPresent("org.flowable.spring.SpringProcessEngineConfiguration", beanFactory.getBeanClassLoader())) { + return null; + } + + if (!beanFactory.containsBean("springProcessEngineConfiguration")) { + return null; + } + + FlowableProperties properties = beanFactory.getBeanProvider(FlowableProperties.class) + .getIfAvailable(); + if (properties == null || !properties.isCheckProcessDefinitions()) { + return null; + } + List locationSuffixes = properties.getProcessDefinitionLocationSuffixes(); + if (locationSuffixes.isEmpty()) { + return null; + } + String locationPrefix = properties.getProcessDefinitionLocationPrefix(); + if (locationPrefix.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX) || locationPrefix.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX)) { + return new ProcessAutoDeployResourceContribution(locationPrefix, locationSuffixes, beanFactory); + } + + return null; + } + + static class ProcessAutoDeployResourceContribution extends BaseAutoDeployResourceContribution { + + protected final ConfigurableListableBeanFactory beanFactory; + + public ProcessAutoDeployResourceContribution(String locationPrefix, Collection locationSuffixes, ConfigurableListableBeanFactory beanFactory) { + super(locationPrefix, locationSuffixes); + this.beanFactory = beanFactory; + } + + @Override + protected void applyToResource(ClassPathResource resource, RuntimeHints hints) { + super.applyToResource(resource, hints); + BpmnXMLConverter xmlConverter = new BpmnXMLConverter(); + BpmnModel bpmnModel = xmlConverter.convertToBpmnModel(() -> { + try { + return resource.getInputStream(); + } catch (IOException e) { + throw new UncheckedIOException("Failed to read resource " + resource, e); + } + }, false, false); + Collection serviceTasks = bpmnModel.getMainProcess().findFlowElementsOfType(ServiceTask.class); + for (ServiceTask serviceTask : serviceTasks) { + if (ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION.equals(serviceTask.getImplementationType())) { + String expression = serviceTask.getImplementation(); + String expressionWithoutDelimiters = expression.substring(2); + expressionWithoutDelimiters = expressionWithoutDelimiters.substring(0, expressionWithoutDelimiters.length() - 1); + String beanName = expressionWithoutDelimiters; + try { + BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName); + String beanClassName = beanDefinition.getBeanClassName(); + if (StringUtils.isNotEmpty(beanClassName)) { + hints.reflection().registerType(TypeReference.of(beanClassName), MemberCategory.values()); + logger.debug("Registering hint for bean name [{}] for service task {} in {}", beanName, serviceTask.getId(), resource); + } else { + logger.debug("No bean class name for bean name [{}] for service task {} in {}", beanName, serviceTask.getId(), resource); + } + + } catch (Throwable throwable) { + logger.error("Couldn't find bean named [{}] for service task {} in {}", beanName, serviceTask.getId(), resource); + } + + } + } + + } + } +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/environment/FlowableDefaultPropertiesLocationRuntimeHints.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/environment/FlowableDefaultPropertiesLocationRuntimeHints.java new file mode 100644 index 00000000000..17738ffdd48 --- /dev/null +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/java/org/flowable/spring/boot/environment/FlowableDefaultPropertiesLocationRuntimeHints.java @@ -0,0 +1,65 @@ +/* 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. + */ +package org.flowable.spring.boot.environment; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.support.FilePatternResourceHintsRegistrar; +import org.springframework.boot.env.PropertySourceLoader; +import org.springframework.core.io.support.SpringFactoriesLoader; + +/** + * @author Filip Hrisafov + */ +public class FlowableDefaultPropertiesLocationRuntimeHints implements RuntimeHintsRegistrar { + // This is similar to what is being done in ConfigDataLocationRuntimeHints + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + // This will register all the flowable-default configuration properties + FilePatternResourceHintsRegistrar.forClassPathLocations("/") + .withFileExtensions(getExtensions(classLoader)) + .withFilePrefixes("flowable-default") + .registerHints(hints.resources(), classLoader); + + } + + // The logic below is the same as for the ConfigDataLocationRuntimeHints + /** + * Get the application file extensions to consider. A valid extension starts with a + * dot. + * @param classLoader the classloader to use + * @return the configuration file extensions + */ + protected List getExtensions(ClassLoader classLoader) { + List extensions = new ArrayList<>(); + List propertySourceLoaders = getSpringFactoriesLoader(classLoader) + .load(PropertySourceLoader.class); + for (PropertySourceLoader propertySourceLoader : propertySourceLoaders) { + for (String fileExtension : propertySourceLoader.getFileExtensions()) { + String candidate = "." + fileExtension; + if (!extensions.contains(candidate)) { + extensions.add(candidate); + } + } + } + return extensions; + } + + protected SpringFactoriesLoader getSpringFactoriesLoader(ClassLoader classLoader) { + return SpringFactoriesLoader.forDefaultResourceLocation(classLoader); + } +} diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/spring/aot.factories index 6fbe8ed6816..94e8b6c6e34 100644 --- a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/spring/aot.factories +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/main/resources/META-INF/spring/aot.factories @@ -1,5 +1,7 @@ org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\ -org.flowable.spring.aot.FlowableBeanFactoryInitializationAotProcessor,\ -org.flowable.spring.aot.FlowableMybatisMappersBeanFactoryInitializationAotProcessor,\ -org.flowable.spring.aot.MybatisGlobalBeanFactoryInitializationAotProcessor,\ -org.flowable.spring.aot.MybatisApplicationSpecificBeanFactoryInitializationAotProcessor \ No newline at end of file +org.flowable.spring.boot.aot.cmmn.FlowableCmmnAutoDeployBeanFactoryInitializationAotProcessor,\ +org.flowable.spring.boot.aot.dmn.FlowableDmnAutoDeployBeanFactoryInitializationAotProcessor,\ +org.flowable.spring.boot.aot.process.FlowableProcessAutoDeployBeanFactoryInitializationAotProcessor + +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.flowable.spring.boot.environment.FlowableDefaultPropertiesLocationRuntimeHints diff --git a/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/aot/FlowableTaskServiceRuntimeHints.java b/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/aot/FlowableTaskServiceRuntimeHints.java new file mode 100644 index 00000000000..d69afabbe49 --- /dev/null +++ b/modules/flowable-task-service/src/main/java/org/flowable/task/service/impl/aot/FlowableTaskServiceRuntimeHints.java @@ -0,0 +1,30 @@ +/* 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. + */ +package org.flowable.task.service.impl.aot; + +import org.flowable.common.engine.impl.aot.FlowableSqlResourceHintsRegistrar; +import org.springframework.aot.hint.ResourceHints; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; + +/** + * @author Filip Hrisafov + */ +public class FlowableTaskServiceRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + ResourceHints resourceHints = hints.resources(); + FlowableSqlResourceHintsRegistrar.registerSqlResources("org/flowable/task/service/db", resourceHints); + } +} diff --git a/modules/flowable-task-service/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-task-service/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000000..67fb77a12ab --- /dev/null +++ b/modules/flowable-task-service/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.flowable.task.service.impl.aot.FlowableTaskServiceRuntimeHints diff --git a/modules/flowable-variable-service/src/main/java/org/flowable/variable/service/impl/aot/FlowableVariableServiceRuntimeHints.java b/modules/flowable-variable-service/src/main/java/org/flowable/variable/service/impl/aot/FlowableVariableServiceRuntimeHints.java new file mode 100644 index 00000000000..da85a787439 --- /dev/null +++ b/modules/flowable-variable-service/src/main/java/org/flowable/variable/service/impl/aot/FlowableVariableServiceRuntimeHints.java @@ -0,0 +1,34 @@ +/* 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. + */ +package org.flowable.variable.service.impl.aot; + +import org.flowable.common.engine.impl.aot.FlowableSqlResourceHintsRegistrar; +import org.flowable.variable.service.impl.db.IbatisVariableTypeHandler; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.ResourceHints; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; + +/** + * @author Filip Hrisafov + */ +public class FlowableVariableServiceRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + ResourceHints resourceHints = hints.resources(); + FlowableSqlResourceHintsRegistrar.registerSqlResources("org/flowable/variable/service/db", resourceHints); + hints.reflection() + .registerType(IbatisVariableTypeHandler.class, MemberCategory.values()); + } +} diff --git a/modules/flowable-variable-service/src/main/resources/META-INF/spring/aot.factories b/modules/flowable-variable-service/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000000..45df3f88e19 --- /dev/null +++ b/modules/flowable-variable-service/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.flowable.variable.service.impl.aot.FlowableVariableServiceRuntimeHints diff --git a/pom.xml b/pom.xml index 3a76fe2bcb9..d8ee6d15faa 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,7 @@ 2.0.9 4.0.15 3.4.0 + 0.9.27 4.13.2 5.9.3 @@ -710,6 +711,12 @@ + + org.graalvm.buildtools + native-maven-plugin + ${native-build-tools-plugin.version} + true + org.codehaus.mojo exec-maven-plugin From 1f6a889df7b7451ca4a1935b5fabbcd2b3dade29 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 28 Jan 2024 01:13:45 +0100 Subject: [PATCH 06/10] Use correct parameter type in MyBatis mappers This makes sure that our logic for registering runtime hints works as expected and we don't need to register certain classes manually --- .../flowable/job/service/db/mapping/entity/DeadLetterJob.xml | 4 ++-- .../flowable/job/service/db/mapping/entity/SuspendedJob.xml | 4 ++-- .../variable/service/db/mapping/entity/VariableInstance.xml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/DeadLetterJob.xml b/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/DeadLetterJob.xml index f938615bcc2..55ccc930bb1 100644 --- a/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/DeadLetterJob.xml +++ b/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/DeadLetterJob.xml @@ -226,7 +226,7 @@ SELECT * FROM dual - ${limitBefore} SELECT RES.* ${limitBetween} @@ -234,7 +234,7 @@ ${limitAfter} - select count(RES.ID_) diff --git a/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/SuspendedJob.xml b/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/SuspendedJob.xml index 32a1b565a42..84a5cb40765 100644 --- a/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/SuspendedJob.xml +++ b/modules/flowable-job-service/src/main/resources/org/flowable/job/service/db/mapping/entity/SuspendedJob.xml @@ -234,7 +234,7 @@ SELECT * FROM dual - ${limitBefore} SELECT RES.* ${limitBetween} @@ -242,7 +242,7 @@ ${limitAfter} - select count(RES.ID_) diff --git a/modules/flowable-variable-service/src/main/resources/org/flowable/variable/service/db/mapping/entity/VariableInstance.xml b/modules/flowable-variable-service/src/main/resources/org/flowable/variable/service/db/mapping/entity/VariableInstance.xml index 7d020c8cf3a..d56ce95e7c3 100644 --- a/modules/flowable-variable-service/src/main/resources/org/flowable/variable/service/db/mapping/entity/VariableInstance.xml +++ b/modules/flowable-variable-service/src/main/resources/org/flowable/variable/service/db/mapping/entity/VariableInstance.xml @@ -186,7 +186,7 @@ select * from ${prefix}ACT_RU_VARIABLE where ID_ = #{id, jdbcType=VARCHAR} - select * from ${prefix}ACT_RU_VARIABLE From f0864bced3b058ca9120aebe87764cd9510d685e Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 28 Jan 2024 01:15:04 +0100 Subject: [PATCH 07/10] Case definition diagram should not be created in native mode --- .../deployer/CaseDefinitionDiagramHelper.java | 5 +++ .../common/engine/impl/util/NativeUtil.java | 37 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/util/NativeUtil.java diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/deployer/CaseDefinitionDiagramHelper.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/deployer/CaseDefinitionDiagramHelper.java index 38d9e5bdb3f..299b6f6b44e 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/deployer/CaseDefinitionDiagramHelper.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/deployer/CaseDefinitionDiagramHelper.java @@ -20,6 +20,7 @@ import org.flowable.cmmn.model.CmmnModel; import org.flowable.common.engine.api.repository.EngineDeployment; import org.flowable.common.engine.impl.util.IoUtil; +import org.flowable.common.engine.impl.util.NativeUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -74,6 +75,10 @@ protected CmmnResourceEntity createResourceEntity() { } public boolean shouldCreateDiagram(CaseDefinitionEntity caseDefinition, EngineDeployment deployment) { + if (NativeUtil.inNativeImage()) { + // Do not create diagram in native image + return false; + } if (deployment.isNew() && caseDefinition.hasGraphicalNotation() && CommandContextUtil.getCmmnEngineConfiguration().isCreateDiagramOnDeploy()) { diff --git a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/util/NativeUtil.java b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/util/NativeUtil.java new file mode 100644 index 00000000000..4c206628fb3 --- /dev/null +++ b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/util/NativeUtil.java @@ -0,0 +1,37 @@ +/* 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. + */ +package org.flowable.common.engine.impl.util; + +/** + * A util for detecting a GraalVM native environment. + * This is inspired by the Spring {@code NativeDetector} + * + * @author Filip Hrisafov + */ +public class NativeUtil { + + // See https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java + private static final String imageCode = System.getProperty("org.graalvm.nativeimage.imagecode"); + + private static final boolean inNativeImage = (imageCode != null); + + /** + * Returns {@code true} if running in a native image context (for example + * {@code buildtime}, {@code runtime}, or {@code agent}) expressed by setting the + * {@code org.graalvm.nativeimage.imagecode} system property to any value. + */ + public static boolean inNativeImage() { + return inNativeImage; + } + +} From 685780c03c6f2470d244d0b37e046cc0a5617ff0 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 8 Feb 2024 13:59:38 +0100 Subject: [PATCH 08/10] Upgrade MyBatis to 3.15.15 and MyBatis Spring to 3.0.3 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d8ee6d15faa..3b55b510bc9 100644 --- a/pom.xml +++ b/pom.xml @@ -293,12 +293,12 @@ org.mybatis mybatis - 3.5.11 + 3.5.15 org.mybatis mybatis-spring - 3.0.0 + 3.0.3 org.mockito From 5f56cc4cd688c20dc38a06a3cd5776763742a9c0 Mon Sep 17 00:00:00 2001 From: Pavel Horal Date: Thu, 8 Feb 2024 09:51:10 +0100 Subject: [PATCH 09/10] Fix broken OSGi wiring (#3844) --- modules/flowable-batch-service/pom.xml | 3 +++ modules/flowable-entitylink-service/pom.xml | 3 +++ modules/flowable-eventsubscription-service/pom.xml | 3 +++ modules/flowable-identitylink-service/pom.xml | 3 +++ modules/flowable-job-service/pom.xml | 7 ++++--- modules/flowable-task-service/pom.xml | 3 +++ modules/flowable-variable-service/pom.xml | 1 + 7 files changed, 20 insertions(+), 3 deletions(-) diff --git a/modules/flowable-batch-service/pom.xml b/modules/flowable-batch-service/pom.xml index ef9d18327e5..c7dd15fe9b3 100755 --- a/modules/flowable-batch-service/pom.xml +++ b/modules/flowable-batch-service/pom.xml @@ -89,6 +89,9 @@ org.flowable.batch.service.db.mapping.entity + + org.springframework*;resolution:=optional + diff --git a/modules/flowable-entitylink-service/pom.xml b/modules/flowable-entitylink-service/pom.xml index 3a179018067..a2e5d3707fe 100755 --- a/modules/flowable-entitylink-service/pom.xml +++ b/modules/flowable-entitylink-service/pom.xml @@ -94,6 +94,9 @@ org.flowable.entitylink.service.db.mapping.entity + + org.springframework*;resolution:=optional + diff --git a/modules/flowable-eventsubscription-service/pom.xml b/modules/flowable-eventsubscription-service/pom.xml index 6622c802bc1..d6bd9a14b4e 100755 --- a/modules/flowable-eventsubscription-service/pom.xml +++ b/modules/flowable-eventsubscription-service/pom.xml @@ -98,6 +98,9 @@ org.flowable.eventsubscription.service.db.mapping.entity + + org.springframework*;resolution:=optional + diff --git a/modules/flowable-identitylink-service/pom.xml b/modules/flowable-identitylink-service/pom.xml index 951fe638235..6aec9a0b812 100755 --- a/modules/flowable-identitylink-service/pom.xml +++ b/modules/flowable-identitylink-service/pom.xml @@ -94,6 +94,9 @@ org.flowable.identitylink.service.db.mapping.entity + + org.springframework*;resolution:=optional + diff --git a/modules/flowable-job-service/pom.xml b/modules/flowable-job-service/pom.xml index 22bad64cdbd..6b9c69dd079 100755 --- a/modules/flowable-job-service/pom.xml +++ b/modules/flowable-job-service/pom.xml @@ -104,9 +104,10 @@ org.flowable.job.service.db.mapping.entity - - jakarta.enterprise.concurrent;resolution:=optional, - + + jakarta.enterprise.concurrent;resolution:=optional, + org.springframework*;resolution:=optional + diff --git a/modules/flowable-task-service/pom.xml b/modules/flowable-task-service/pom.xml index a6e5f184d08..0cd7214d797 100755 --- a/modules/flowable-task-service/pom.xml +++ b/modules/flowable-task-service/pom.xml @@ -98,6 +98,9 @@ org.flowable.task.service.db.mapping.entity + + org.springframework*;resolution:=optional + diff --git a/modules/flowable-variable-service/pom.xml b/modules/flowable-variable-service/pom.xml index 3117f39bd9a..6e8fb357608 100755 --- a/modules/flowable-variable-service/pom.xml +++ b/modules/flowable-variable-service/pom.xml @@ -96,6 +96,7 @@ jakarta.persistence*;resolution:=optional, + org.springframework*;resolution:=optional From 98fde0c00227ffd541906fbb19f9018c882bc32a Mon Sep 17 00:00:00 2001 From: Tijs Rademakers Date: Thu, 8 Feb 2024 21:51:02 +0100 Subject: [PATCH 10/10] Support listening to a variable change event with multiple elements in bpmn and cmmn --- ...aluateVariableEventListenersOperation.java | 10 +-- .../VariableEventListenerTest.java | 68 +++++++++++++++++++ ...TriggerMultipleVariableEventListeners.cmmn | 33 +++++++++ .../VariableListenerSessionData.java | 12 +++- ...ableListenerEventDefinitionsOperation.java | 6 +- .../variable/VariableListenerEventTest.java | 22 ++++++ ....multipleCatchVariableListeners.bpmn20.xml | 51 ++++++++++++++ 7 files changed, 195 insertions(+), 7 deletions(-) create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.testTriggerMultipleVariableEventListeners.cmmn create mode 100644 modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.multipleCatchVariableListeners.bpmn20.xml diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/EvaluateVariableEventListenersOperation.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/EvaluateVariableEventListenersOperation.java index c5ef2fec146..737b716b092 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/EvaluateVariableEventListenersOperation.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/EvaluateVariableEventListenersOperation.java @@ -93,10 +93,12 @@ public void run() { if (changeTypeValue.equals(variableListenerData.getChangeType()) || VariableListenerEventDefinition.CHANGE_TYPE_ALL.equals(changeTypeValue) || (VariableListenerEventDefinition.CHANGE_TYPE_UPDATE_CREATE.equals(changeTypeValue) && (VariableListenerEventDefinition.CHANGE_TYPE_CREATE.equals(variableListenerData.getChangeType()) || VariableListenerEventDefinition.CHANGE_TYPE_UPDATE.equals(variableListenerData.getChangeType())))) { - - itVariableListener.remove(); - CommandContextUtil.getAgenda().planTriggerPlanItemInstanceOperation(planItemInstance); - triggeredPlanItemInstance = true; + + if (!variableListenerData.containsProcessedElementId(planItemInstance.getPlanItemDefinitionId())) { + CommandContextUtil.getAgenda().planTriggerPlanItemInstanceOperation(planItemInstance); + triggeredPlanItemInstance = true; + variableListenerData.addProcessedElementId(planItemInstance.getPlanItemDefinitionId()); + } } } } diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.java index 1d08e3b7f26..ce4761f5605 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.java @@ -196,6 +196,74 @@ public void testTriggerVariableEventListenerInStageOnlyCreate() { assertCaseInstanceEnded(caseInstance); } + + @Test + @CmmnDeployment + public void testTriggerMultipleVariableEventListeners() { + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("variableListener").start(); + + // 5 plan items reachable + assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().count()).isEqualTo(5); + assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().list()) + .extracting(PlanItemInstance::getPlanItemDefinitionType, PlanItemInstance::getPlanItemDefinitionId, PlanItemInstance::getState) + .containsExactlyInAnyOrder( + tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener", PlanItemInstanceState.AVAILABLE), + tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener2", PlanItemInstanceState.AVAILABLE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskA", PlanItemInstanceState.ACTIVE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskB", PlanItemInstanceState.AVAILABLE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskC", PlanItemInstanceState.AVAILABLE) + ); + + if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, cmmnEngineConfiguration)) { + assertThat(cmmnHistoryService.createHistoricPlanItemInstanceQuery().list()) + .extracting(HistoricPlanItemInstance::getPlanItemDefinitionType, HistoricPlanItemInstance::getPlanItemDefinitionId, HistoricPlanItemInstance::getState) + .containsExactlyInAnyOrder( + tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener", PlanItemInstanceState.AVAILABLE), + tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener2", PlanItemInstanceState.AVAILABLE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskA", PlanItemInstanceState.ACTIVE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskB", PlanItemInstanceState.AVAILABLE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskC", PlanItemInstanceState.AVAILABLE) + ); + } + + // create different variable + cmmnRuntimeService.setVariable(caseInstance.getId(), "var2", "test"); + + assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().planItemDefinitionType(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER).count()).isEqualTo(2); + + // create var1 variable to trigger variable event listener + cmmnRuntimeService.setVariable(caseInstance.getId(), "var1", "test"); + + // variable event listener should be completed + assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().planItemDefinitionType(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER).count()).isZero(); + assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().list()) + .extracting(PlanItemInstance::getPlanItemDefinitionType, PlanItemInstance::getPlanItemDefinitionId, PlanItemInstance::getState) + .containsExactlyInAnyOrder( + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskA", PlanItemInstanceState.ACTIVE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskB", PlanItemInstanceState.ACTIVE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskC", PlanItemInstanceState.ACTIVE) + ); + + assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().planItemDefinitionId("taskB").planItemInstanceStateActive().count()).isEqualTo(1); + assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().planItemDefinitionId("taskC").planItemInstanceStateActive().count()).isEqualTo(1); + + if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, cmmnEngineConfiguration)) { + assertThat(cmmnHistoryService.createHistoricPlanItemInstanceQuery().list()) + .extracting(HistoricPlanItemInstance::getPlanItemDefinitionType, HistoricPlanItemInstance::getPlanItemDefinitionId, HistoricPlanItemInstance::getState) + .containsExactlyInAnyOrder( + tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener", PlanItemInstanceState.COMPLETED), + tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener2", PlanItemInstanceState.COMPLETED), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskA", PlanItemInstanceState.ACTIVE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskB", PlanItemInstanceState.ACTIVE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskC", PlanItemInstanceState.ACTIVE) + ); + } + + + assertCaseInstanceNotEnded(caseInstance); + cmmnTaskService.createTaskQuery().list().forEach(t -> cmmnTaskService.complete(t.getId())); + assertCaseInstanceEnded(caseInstance); + } @Test @CmmnDeployment diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.testTriggerMultipleVariableEventListeners.cmmn b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.testTriggerMultipleVariableEventListeners.cmmn new file mode 100644 index 00000000000..8b1390ca4cc --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.testTriggerMultipleVariableEventListeners.cmmn @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + occur + + + + + occur + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/variablelistener/VariableListenerSessionData.java b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/variablelistener/VariableListenerSessionData.java index d21d3d5e916..99049a3af51 100644 --- a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/variablelistener/VariableListenerSessionData.java +++ b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/variablelistener/VariableListenerSessionData.java @@ -12,6 +12,9 @@ */ package org.flowable.common.engine.impl.variablelistener; +import java.util.ArrayList; +import java.util.List; + public class VariableListenerSessionData { public static final String VARIABLE_CREATE = "create"; @@ -22,6 +25,7 @@ public class VariableListenerSessionData { protected String scopeId; protected String scopeType; protected String scopeDefinitionId; + protected List processedElementIds = new ArrayList<>(); public VariableListenerSessionData(String changeType, String scopeId, String scopeType, String scopeDefinitionId) { this.changeType = changeType; @@ -57,5 +61,11 @@ public String getScopeDefinitionId() { } public void setScopeDefinitionId(String scopeDefinitionId) { this.scopeDefinitionId = scopeDefinitionId; - } + } + public boolean containsProcessedElementId(String elementId) { + return this.processedElementIds.contains(elementId); + } + public void addProcessedElementId(String elementId) { + this.processedElementIds.add(elementId); + } } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/EvaluateVariableListenerEventDefinitionsOperation.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/EvaluateVariableListenerEventDefinitionsOperation.java index 8691987d497..ac6e2bce53a 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/EvaluateVariableListenerEventDefinitionsOperation.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/EvaluateVariableListenerEventDefinitionsOperation.java @@ -106,8 +106,10 @@ public void run() { VariableListenerEventDefinition.CHANGE_TYPE_ALL.equals(changeTypeValue) || (VariableListenerEventDefinition.CHANGE_TYPE_UPDATE_CREATE.equals(changeTypeValue) && (VariableListenerEventDefinition.CHANGE_TYPE_CREATE.equals(variableListenerData.getChangeType()) || VariableListenerEventDefinition.CHANGE_TYPE_UPDATE.equals(variableListenerData.getChangeType())))) { - itVariableListener.remove(); - CommandContextUtil.getAgenda().planTriggerExecutionOperation(execution); + if (!variableListenerData.containsProcessedElementId(execution.getActivityId())) { + CommandContextUtil.getAgenda().planTriggerExecutionOperation(execution); + variableListenerData.addProcessedElementId(execution.getActivityId()); + } } } } diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.java index 6291fc4fb72..15661b7efc0 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.java @@ -88,6 +88,28 @@ public void catchVariableListenerUpdate() { assertThat(runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).count()).isEqualTo(0); } + @Test + @Deployment + public void multipleCatchVariableListeners() { + ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("catchVariableListener"); + + assertThat(runtimeService.createEventSubscriptionQuery().processInstanceId(processInstance.getId()).list()).hasSize(2); + + assertThat(runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).count()).isEqualTo(3); + + runtimeService.setVariable(processInstance.getId(), "var2", "test"); + + assertThat(runtimeService.createEventSubscriptionQuery().processInstanceId(processInstance.getId()).list()).hasSize(2); + + runtimeService.setVariable(processInstance.getId(), "var1", "test"); + + assertThat(runtimeService.createEventSubscriptionQuery().processInstanceId(processInstance.getId()).list()).hasSize(0); + + assertThat(taskService.createTaskQuery().processInstanceId(processInstance.getId()).count()).isEqualTo(2); + assertThat(taskService.createTaskQuery().taskDefinitionKey("aftertask").processInstanceId(processInstance.getId()).count()).isEqualTo(1); + assertThat(taskService.createTaskQuery().taskDefinitionKey("aftertask2").processInstanceId(processInstance.getId()).count()).isEqualTo(1); + } + @Test @Deployment public void boundaryVariableListener() { diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.multipleCatchVariableListeners.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.multipleCatchVariableListeners.bpmn20.xml new file mode 100644 index 00000000000..f3595d15fc9 --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.multipleCatchVariableListeners.bpmn20.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +