/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.camel.cdi; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.net.URL; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.stream.Stream; import static java.lang.String.format; import static java.util.Collections.addAll; import static java.util.Collections.emptySet; import static java.util.Collections.singleton; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toSet; import javax.enterprise.inject.CreationException; import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.BeanManager; import javax.xml.bind.JAXBException; import org.apache.camel.cdi.xml.ApplicationContextFactoryBean; import org.apache.camel.cdi.xml.BeanManagerAware; import org.apache.camel.cdi.xml.CamelContextFactoryBean; import org.apache.camel.cdi.xml.ErrorHandlerDefinition; import org.apache.camel.cdi.xml.ErrorHandlerType; import org.apache.camel.cdi.xml.ImportDefinition; import org.apache.camel.cdi.xml.RestContextDefinition; import org.apache.camel.cdi.xml.RouteContextDefinition; import org.apache.camel.core.xml.AbstractCamelFactoryBean; import org.apache.camel.core.xml.CamelProxyFactoryDefinition; import org.apache.camel.core.xml.CamelServiceExporterDefinition; import org.apache.camel.impl.DefaultCamelContext; import org.apache.camel.model.IdentifiedType; import org.apache.camel.model.OptionalIdentifiedDefinition; import org.apache.camel.model.RouteDefinition; import org.apache.camel.model.RoutesDefinition; import org.apache.camel.model.rest.RestDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.apache.camel.cdi.AnyLiteral.ANY; import static org.apache.camel.cdi.ApplicationScopedLiteral.APPLICATION_SCOPED; import static org.apache.camel.cdi.DefaultLiteral.DEFAULT; import static org.apache.camel.cdi.ResourceHelper.getResource; import static org.apache.camel.cdi.Startup.Literal.STARTUP; import static org.apache.camel.util.ObjectHelper.isEmpty; import static org.apache.camel.util.ObjectHelper.isNotEmpty; final class XmlCdiBeanFactory { private final Logger logger = LoggerFactory.getLogger(XmlCdiBeanFactory.class); private final BeanManager manager; private final CdiCamelEnvironment environment; private final CdiCamelExtension extension; private XmlCdiBeanFactory(BeanManager manager, CdiCamelEnvironment environment, CdiCamelExtension extension) { this.manager = manager; this.environment = environment; this.extension = extension; } static XmlCdiBeanFactory with(BeanManager manager, CdiCamelEnvironment environment, CdiCamelExtension extension) { return new XmlCdiBeanFactory(manager, environment, extension); } Set<SyntheticBean<?>> beansFrom(String path) throws JAXBException, IOException { URL url = getResource(path); if (url == null) { logger.warn("Unable to locate resource [{}] for import!", path); return emptySet(); } return beansFrom(url); } Set<SyntheticBean<?>> beansFrom(URL url) throws JAXBException, IOException { try (InputStream xml = url.openStream()) { Object node = XmlCdiJaxbContexts.CAMEL_CDI.instance() .createUnmarshaller() .unmarshal(xml); if (node instanceof RoutesDefinition) { RoutesDefinition routes = (RoutesDefinition) node; return singleton(routesDefinitionBean(routes, url)); } else if (node instanceof ApplicationContextFactoryBean) { ApplicationContextFactoryBean app = (ApplicationContextFactoryBean) node; Set<SyntheticBean<?>> beans = new HashSet<>(); for (CamelContextFactoryBean factory : app.getContexts()) { SyntheticBean<?> bean = camelContextBean(factory, url); beans.add(bean); beans.addAll(camelContextBeans(factory, bean, url)); } for (ErrorHandlerDefinition definition : app.getErrorHandlers()) { beans.add(errorHandlerBean(definition, url)); } for (ImportDefinition definition : app.getImports()) { // Get the base URL as imports are relative to this String path = url.getFile().substring(0, url.getFile().lastIndexOf('/')); String base = url.getProtocol() + "://" + url.getHost() + path; beans.addAll(beansFrom(base + "/" + definition.getResource())); } for (RestContextDefinition factory : app.getRestContexts()) { beans.add(restContextBean(factory, url)); } for (RouteContextDefinition factory : app.getRouteContexts()) { beans.add(routeContextBean(factory, url)); } for (AbstractCamelFactoryBean<?> factory : app.getBeans()) { if (hasId(factory)) { beans.add(camelContextBean(null, factory, url)); } } return beans; } else if (node instanceof CamelContextFactoryBean) { CamelContextFactoryBean factory = (CamelContextFactoryBean) node; Set<SyntheticBean<?>> beans = new HashSet<>(); SyntheticBean<?> bean = camelContextBean(factory, url); beans.add(bean); beans.addAll(camelContextBeans(factory, bean, url)); return beans; } else if (node instanceof RestContextDefinition) { RestContextDefinition factory = (RestContextDefinition) node; return singleton(restContextBean(factory, url)); } else if (node instanceof RouteContextDefinition) { RouteContextDefinition factory = (RouteContextDefinition) node; return singleton(routeContextBean(factory, url)); } } return emptySet(); } private SyntheticBean<?> camelContextBean(CamelContextFactoryBean factory, URL url) { Set<Annotation> annotations = new HashSet<>(); annotations.add(ANY); if (hasId(factory)) { addAll(annotations, ContextName.Literal.of(factory.getId()), NamedLiteral.of(factory.getId())); } else { annotations.add(DEFAULT); factory.setImplicitId(true); factory.setId(new CdiCamelContextNameStrategy().getNextName()); } annotations.add(APPLICATION_SCOPED); SyntheticAnnotated annotated = new SyntheticAnnotated(DefaultCamelContext.class, manager.createAnnotatedType(DefaultCamelContext.class).getTypeClosure(), annotations); return new SyntheticBean<>(manager, annotated, DefaultCamelContext.class, environment.camelContextInjectionTarget( new SyntheticInjectionTarget<>(() -> { DefaultCamelContext context = new DefaultCamelContext(); factory.setContext(context); factory.setBeanManager(manager); return context; }, context -> { try { factory.afterPropertiesSet(); } catch (Exception cause) { throw new CreationException(cause); } }), annotated, manager, extension), bean -> "imported Camel context with " + (factory.isImplicitId() ? "implicit " : "") + "id [" + factory.getId() + "] " + "from resource [" + url + "] " + "with qualifiers " + bean.getQualifiers()); } private Set<SyntheticBean<?>> camelContextBeans(CamelContextFactoryBean factory, Bean<?> context, URL url) { Set<SyntheticBean<?>> beans = new HashSet<>(); // TODO: WARN log if the definition doesn't have an id if (factory.getBeansFactory() != null) { factory.getBeansFactory().stream() .filter(XmlCdiBeanFactory::hasId) .map(bean -> camelContextBean(context, bean, url)) .forEach(beans::add); } // TODO: define in beans if (factory.getEndpoints() != null) { factory.getEndpoints().stream() .filter(XmlCdiBeanFactory::hasId) .map(endpoint -> camelContextBean(context, endpoint, url)) .forEach(beans::add); } if (factory.getErrorHandlers() != null) { factory.getErrorHandlers().stream() .filter(XmlCdiBeanFactory::hasId) .map(handler -> errorHandlerBean(handler, url)) .forEach(beans::add); } if (factory.getExports() != null) { factory.getExports().stream() .map(export -> serviceExporterBean(context, export, url)) .forEach(beans::add); } if (factory.getProxies() != null) { factory.getProxies().stream() .filter(XmlCdiBeanFactory::hasId) .map(proxy -> proxyFactoryBean(context, proxy, url)) .forEach(beans::add); } // TODO: define in beans if (factory.getRedeliveryPolicies() != null) { factory.getRedeliveryPolicies().stream() .filter(XmlCdiBeanFactory::hasId) .map(policy -> camelContextBean(context, policy, url)) .forEach(beans::add); } return beans; } private SyntheticBean<?> camelContextBean(Bean<?> context, AbstractCamelFactoryBean<?> factory, URL url) { if (factory instanceof BeanManagerAware) { ((BeanManagerAware) factory).setBeanManager(manager); } Set<Annotation> annotations = new HashSet<>(); annotations.add(ANY); // FIXME: should add @ContextName if the Camel context bean has it annotations.add(hasId(factory) ? NamedLiteral.of(factory.getId()) : DEFAULT); // TODO: should that be @Singleton to enable injection points with bean instance type? if (factory.isSingleton()) { annotations.add(APPLICATION_SCOPED); } return new SyntheticBean<>(manager, new SyntheticAnnotated(factory.getObjectType(), manager.createAnnotatedType(factory.getObjectType()).getTypeClosure(), annotations), factory.getObjectType(), new XmlFactoryBeanInjectionTarget<>(manager, factory, context), bean -> "imported bean [" + factory.getId() + "] " + "from resource [" + url + "] " + "with qualifiers " + bean.getQualifiers()); } private SyntheticBean<?> proxyFactoryBean(Bean<?> context, CamelProxyFactoryDefinition proxy, URL url) { if (isEmpty(proxy.getServiceRef()) && isEmpty(proxy.getServiceUrl())) { throw new CreationException( format("Missing [%s] or [%s] attribute for imported bean [%s] from resource [%s]", "serviceRef", "serviceUrl", proxy.getId(), url)); } return new XmlProxyFactoryBean<>(manager, new SyntheticAnnotated(proxy.getServiceInterface(), manager.createAnnotatedType(proxy.getServiceInterface()).getTypeClosure(), APPLICATION_SCOPED, ANY, NamedLiteral.of(proxy.getId())), proxy.getServiceInterface(), bean -> "imported bean [" + proxy.getId() + "] " + "from resource [" + url + "] " + "with qualifiers " + bean.getQualifiers(), context, proxy); } private SyntheticBean<?> serviceExporterBean(Bean<?> context, CamelServiceExporterDefinition exporter, URL url) { // TODO: replace with CreationException requireNonNull(exporter.getServiceRef(), () -> format("Missing [%s] attribute for imported bean [%s] from resource [%s]", "serviceRef", Objects.toString(exporter.getId(), "export"), url)); Class<?> type; if (exporter.getServiceInterface() != null) { type = exporter.getServiceInterface(); } else { Bean<?> bean = manager.resolve(manager.getBeans(exporter.getServiceRef())); if (bean != null) { type = bean.getBeanClass(); } else { requireNonNull(exporter.getServiceInterface(), () -> format("Missing [%s] attribute for imported bean [%s] from resource [%s]", "serviceInterface", Objects.toString(exporter.getId(), "export"), url)); type = exporter.getServiceInterface(); } } Set<Type> types = new HashSet<>(manager.createAnnotatedType(type).getTypeClosure()); // Weld does not add Object.class for interfaces as they do not extend Object.class. // Though let's add it so that it's possible to lookup by bean type Object.class // beans whose bean class is an interface (for startup beans). types.add(Object.class); return new XmlServiceExporterBean<>(manager, new SyntheticAnnotated(type, types, APPLICATION_SCOPED, ANY, STARTUP, hasId(exporter) ? NamedLiteral.of(exporter.getId()) : DEFAULT), type, bean -> "imported bean [" + Objects.toString(exporter.getId(), "export") + "] " + "from resource [" + url + "] " + "with qualifiers " + bean.getQualifiers(), context, exporter); } private SyntheticBean<?> restContextBean(RestContextDefinition definition, URL url) { requireNonNull(definition.getId(), () -> format("Missing [%s] attribute for imported bean [%s] from resource [%s]", "id", "restContext", url)); return new SyntheticBean<>(manager, new SyntheticAnnotated(List.class, Stream.of(List.class, new ListParameterizedType(RestDefinition.class)) .collect(toSet()), ANY, NamedLiteral.of(definition.getId())), List.class, new SyntheticInjectionTarget<>(definition::getRests), bean -> "imported rest context with " + "id [" + definition.getId() + "] " + "from resource [" + url + "] " + "with qualifiers " + bean.getQualifiers()); } private SyntheticBean<?> routeContextBean(RouteContextDefinition definition, URL url) { requireNonNull(definition.getId(), () -> format("Missing [%s] attribute for imported bean [%s] from resource [%s]", "id", "routeContext", url)); return new SyntheticBean<>(manager, new SyntheticAnnotated(List.class, Stream.of(List.class, new ListParameterizedType(RouteDefinition.class)) .collect(toSet()), ANY, NamedLiteral.of(definition.getId())), List.class, new SyntheticInjectionTarget<>(definition::getRoutes), bean -> "imported route context with " + "id [" + definition.getId() + "] " + "from resource [" + url + "] " + "with qualifiers " + bean.getQualifiers()); } private SyntheticBean<?> routesDefinitionBean(RoutesDefinition definition, URL url) { return new SyntheticBean<>(manager, // TODO: should be @Named if the id is set new SyntheticAnnotated(RoutesDefinition.class, manager.createAnnotatedType(RoutesDefinition.class).getTypeClosure(), ANY, DEFAULT), RoutesDefinition.class, new SyntheticInjectionTarget<>(() -> definition), bean -> "imported routes definition " + (hasId(definition) ? "[" + definition.getId() + "] " : "") + "from resource [" + url + "]"); } private SyntheticBean<?> errorHandlerBean(ErrorHandlerDefinition definition, URL url) { ErrorHandlerType type = definition.getType(); // Validate attributes according to type if (isNotEmpty(definition.getDeadLetterUri()) && !type.equals(ErrorHandlerType.DeadLetterChannel)) { throw attributeNotSupported("deadLetterUri", type, definition.getId()); } if (isNotEmpty(definition.getDeadLetterHandleNewException()) && !type.equals(ErrorHandlerType.DeadLetterChannel)) { throw attributeNotSupported("deadLetterHandleNewException", type, definition.getId()); } if (isNotEmpty(definition.getTransactionTemplateRef()) && !type.equals(ErrorHandlerType.TransactionErrorHandler)) { throw attributeNotSupported("transactionTemplateRef", type, definition.getId()); } if (isNotEmpty(definition.getTransactionManagerRef()) && !type.equals(ErrorHandlerType.TransactionErrorHandler)) { throw attributeNotSupported("transactionManagerRef", type, definition.getId()); } if (isNotEmpty(definition.getRollbackLoggingLevel()) && (!type.equals(ErrorHandlerType.TransactionErrorHandler))) { throw attributeNotSupported("rollbackLoggingLevel", type, definition.getId()); } if (isNotEmpty(definition.getUseOriginalMessage()) && (type.equals(ErrorHandlerType.LoggingErrorHandler) || type.equals(ErrorHandlerType.NoErrorHandler))) { throw attributeNotSupported("useOriginalMessage", type, definition.getId()); } if (isNotEmpty(definition.getOnRedeliveryRef()) && (type.equals(ErrorHandlerType.LoggingErrorHandler) || type.equals(ErrorHandlerType.NoErrorHandler))) { throw attributeNotSupported("onRedeliveryRef", type, definition.getId()); } if (isNotEmpty(definition.getOnExceptionOccurredRef()) && (type.equals(ErrorHandlerType.LoggingErrorHandler) || type.equals(ErrorHandlerType.NoErrorHandler))) { throw attributeNotSupported("onExceptionOccurredRef", type, definition.getId()); } if (isNotEmpty(definition.getOnPrepareFailureRef()) && (type.equals(ErrorHandlerType.TransactionErrorHandler) || type.equals(ErrorHandlerType.LoggingErrorHandler) || type.equals(ErrorHandlerType.NoErrorHandler))) { throw attributeNotSupported("onPrepareFailureRef", type, definition.getId()); } if (isNotEmpty(definition.getRetryWhileRef()) && (type.equals(ErrorHandlerType.LoggingErrorHandler) || type.equals(ErrorHandlerType.NoErrorHandler))) { throw attributeNotSupported("retryWhileRef", type, definition.getId()); } if (isNotEmpty(definition.getOnRedeliveryRef()) && (type.equals(ErrorHandlerType.LoggingErrorHandler) || type.equals(ErrorHandlerType.NoErrorHandler))) { throw attributeNotSupported("redeliveryPolicyRef", type, definition.getId()); } if (isNotEmpty(definition.getExecutorServiceRef()) && (type.equals(ErrorHandlerType.LoggingErrorHandler) || type.equals(ErrorHandlerType.NoErrorHandler))) { throw attributeNotSupported("executorServiceRef", type, definition.getId()); } if (isNotEmpty(definition.getLogName()) && (!type.equals(ErrorHandlerType.LoggingErrorHandler))) { throw attributeNotSupported("logName", type, definition.getId()); } if (isNotEmpty(definition.getLevel()) && (!type.equals(ErrorHandlerType.LoggingErrorHandler))) { throw attributeNotSupported("level", type, definition.getId()); } return new XmlErrorHandlerFactoryBean(manager, new SyntheticAnnotated(type.getTypeAsClass(), manager.createAnnotatedType(type.getTypeAsClass()).getTypeClosure(), ANY, NamedLiteral.of(definition.getId())), type.getTypeAsClass(), bean -> "imported error handler with " + "id [" + definition.getId() + "] " + "from resource [" + url + "] " + "with qualifiers " + bean.getQualifiers(), definition); } private static CreationException attributeNotSupported(String attribute, ErrorHandlerType type, String id) { return new CreationException( format("Attribute [%s] is not supported by error handler type [%s], in error handler with id [%s]", attribute, type, id)); } private static <T extends IdentifiedType> boolean hasId(T type) { return isNotEmpty(type.getId()); } private static <T extends OptionalIdentifiedDefinition<T>> boolean hasId(T type) { return isNotEmpty(type.getId()); } }