/**
* Copyright 2005-2016 Red Hat, Inc.
*
* Red Hat 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 io.fabric8.cdi.producers;
import io.fabric8.annotations.Configuration;
import io.fabric8.annotations.Endpoint;
import io.fabric8.annotations.External;
import io.fabric8.annotations.Path;
import io.fabric8.annotations.PortName;
import io.fabric8.annotations.Protocol;
import io.fabric8.annotations.ServiceName;
import io.fabric8.cdi.Types;
import io.fabric8.cdi.bean.ConfigurationBean;
import io.fabric8.cdi.bean.ServiceBean;
import io.fabric8.cdi.bean.ServiceUrlBean;
import io.fabric8.cdi.bean.ServiceUrlCollectionBean;
import io.fabric8.cdi.qualifiers.ConfigurationQualifier;
import io.fabric8.cdi.qualifiers.Qualifiers;
import org.apache.deltaspike.core.api.provider.BeanProvider;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.AnnotatedParameter;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.enterprise.inject.spi.Producer;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static io.fabric8.cdi.Utils.or;
public class FactoryMethodProducer<T, X> implements Producer<T> {
private static final String INVOCATION_ERROR_FORMAT = "Failed to invoke @Factory annotated method: %s on bean: %s with arguments: %s";
private static final String PARAMETER_ERROR_FORMAT = "Failed to process @Factory annotated method: %s on bean: %s. Error processing parameter at position: %d. Check method signature for superfluous arguments or missing annotations.";
private static final String SERVICE_LOOKUP_ERROR_FORMAT = "Failed to process @Factory annotated method: %s on bean: %s. Failed to lookup service %s. ";
private static final String BEAN_LOOKUP_ERROR_FORMAT = "Failed to process @Factory annotated method: %s on bean: %s. Failed to lookup bean of type: %s for service: %s. ";
private static final String CONF_LOOKUP_ERROR_FORMAT = "Failed to process @Factory annotated method: %s on bean: %s. Failed to lookup configuration for service: %s. ";
private final Bean<T> bean;
private final AnnotatedMethod<X> factoryMethod;
// The fields below refer to the injection point properties
private final String pointName;
private final String pointProtocol;
private final String pointPort;
private final String pointPath;
// end of injection point properties
public FactoryMethodProducer(Bean<T> bean, AnnotatedMethod<X> factoryMethod, String pointName, String pointProtocol, String pointPort, String pointPath) {
this.bean = bean;
this.factoryMethod = factoryMethod;
this.pointName = pointName;
this.pointProtocol = pointProtocol;
this.pointPort = pointPort;
this.pointPath = pointPath;
}
@Override
public T produce(CreationalContext<T> ctx) {
List<Object> arguments = new ArrayList<>();
for (AnnotatedParameter<X> parameter : factoryMethod.getParameters()) {
Type type = parameter.getBaseType();
ServiceName parameterServiceName = parameter.getAnnotation(ServiceName.class);
Protocol parameterProtocol = parameter.getAnnotation(Protocol.class);
PortName parameterPortName = parameter.getAnnotation(PortName.class);
Path parameterPath = parameter.getAnnotation(Path.class);
Endpoint paramEndpoint = parameter.getAnnotation(Endpoint.class);
External paramExternal = parameter.getAnnotation(External.class);
Configuration configuration = parameter.getAnnotation(Configuration.class);
//A point without @ServiceName is invalid.
// Even if method defines @ServiceName, the annotation on the injection point takes precedence
String serviceName = pointName;
String serviceProtocol = or(pointProtocol, parameterProtocol != null ? parameterProtocol.value() : null);
String servicePort = or(pointPort, parameterPortName != null ? parameterPortName.value() : null);
String servicePath = or(pointPath, parameterPath != null ? parameterPath.value() : null);
Boolean serviceEndpoint = paramEndpoint != null ? paramEndpoint.value() : false;
Boolean serviceExternal = paramExternal != null ? paramExternal.value() : false;
//If the @ServiceName exists on the current String property
if (parameterServiceName != null && String.class.equals(type)) {
try {
String serviceUrl = getServiceUrl(serviceName, serviceProtocol, servicePort, servicePath, serviceEndpoint, serviceExternal, ctx);
arguments.add(serviceUrl);
} catch (Throwable t) {
throw new RuntimeException(String.format(SERVICE_LOOKUP_ERROR_FORMAT,
factoryMethod.getJavaMember().getName(),
factoryMethod.getJavaMember().getDeclaringClass().getName(),
serviceName), t);
}
}
//If the @ServiceName exists on the current List property
else if (parameterServiceName != null && List.class.equals(Types.asClass(type))) {
try {
List<String> endpointList = getEndpointList(serviceName, serviceProtocol, servicePort, servicePath, serviceExternal, ctx);
arguments.add(endpointList);
} catch (Throwable t) {
throw new RuntimeException(String.format(SERVICE_LOOKUP_ERROR_FORMAT,
factoryMethod.getJavaMember().getName(),
factoryMethod.getJavaMember().getDeclaringClass().getName(),
serviceName), t);
}
}
//If the @ServiceName exists on the current List property
else if (parameterServiceName != null && Set.class.equals(Types.asClass(type))) {
try {
List<String> endpointList = getEndpointList(serviceName, serviceProtocol, servicePort, servicePath, serviceExternal, ctx);
arguments.add(new HashSet<>(endpointList));
} catch (Throwable t) {
throw new RuntimeException(String.format(SERVICE_LOOKUP_ERROR_FORMAT,
factoryMethod.getJavaMember().getName(),
factoryMethod.getJavaMember().getDeclaringClass().getName(),
serviceName), t);
}
}
// If the @ServiceName exists on the current property which is a non-String
else if (parameterServiceName != null && !String.class.equals(type)) {
try {
Object serviceBean = getServiceBean(serviceName, serviceProtocol, servicePort, servicePath, serviceEndpoint, serviceExternal, type, ctx);
arguments.add(serviceBean);
} catch (Throwable t) {
throw new RuntimeException(String.format(BEAN_LOOKUP_ERROR_FORMAT,
factoryMethod.getJavaMember().getName(),
factoryMethod.getJavaMember().getDeclaringClass().getName(),
type,
serviceName), t);
}
}
//If the current parameter is annotated with @Configuration
else if (configuration != null) {
try {
Object config = getConfiguration(serviceName, (Class<Object>) type, ctx);
arguments.add(config);
} catch (Throwable t) {
throw new RuntimeException(String.format(CONF_LOOKUP_ERROR_FORMAT,
factoryMethod.getJavaMember().getName(),
factoryMethod.getJavaMember().getDeclaringClass().getName(),
serviceName), t);
}
} else {
try {
Object other = BeanProvider.getContextualReference(Types.asClass(type), true);
arguments.add(other);
} catch (Throwable t) {
throw new RuntimeException(String.format(PARAMETER_ERROR_FORMAT,
factoryMethod.getJavaMember().getName(),
factoryMethod.getJavaMember().getDeclaringClass().getName(),
parameter.getPosition()), t);
}
}
}
try {
return (T) factoryMethod.getJavaMember().invoke(bean.create(ctx), arguments.toArray());
} catch (Throwable t) {
throw new RuntimeException(String.format(INVOCATION_ERROR_FORMAT,
factoryMethod.getJavaMember().getName(),
factoryMethod.getJavaMember().getDeclaringClass().getName(),
arguments), t);
}
}
@Override
public void dispose(T instance) {
}
@Override
public Set<InjectionPoint> getInjectionPoints() {
return Collections.emptySet();
}
/**
* Get Service URL from the context or create a producer.
* @param serviceId
* @param serviceProtocol
* @param context
* @return
*/
private static String getServiceUrl(String serviceId, String serviceProtocol, String servicePort, String servicePath, Boolean serviceEndpoint, Boolean serviceExternal, CreationalContext context) {
try {
return BeanProvider.getContextualReference(String.class, Qualifiers.create(serviceId, serviceProtocol, servicePort, servicePath, serviceEndpoint, serviceExternal));
} catch (IllegalStateException e) {
//Contextual Refernece not found, let's fallback to Configuration Producer.
Producer<String> producer = ServiceUrlBean.anyBean(serviceId, serviceProtocol, servicePort, servicePath, serviceEndpoint, serviceExternal).getProducer();
if (producer != null) {
return ServiceUrlBean.anyBean(serviceId, serviceProtocol, servicePort, servicePath, serviceEndpoint, serviceExternal).getProducer().produce(context);
} else {
throw new IllegalStateException("Could not find producer for service:" + serviceId + " protocol:" + serviceProtocol);
}
}
}
/**
* Get Endpoint URLs as List from the context or create a producer.
* @param serviceId
* @param serviceProtocol
* @param context
* @return
*/
private static List<String> getEndpointList(String serviceId, String serviceProtocol, String servicePort, String servicePath, Boolean serviceExternal, CreationalContext context) {
final Boolean serviceEndpoint = true;
try {
return BeanProvider.getContextualReference(List.class, Qualifiers.create(serviceId, serviceProtocol, servicePort, servicePath, serviceEndpoint, serviceExternal));
} catch (IllegalStateException e) {
//Contextual Refernece not found, let's fallback to Configuration Producer.
Producer<String> producer = ServiceUrlBean.anyBean(serviceId, serviceProtocol, servicePort, servicePath, serviceEndpoint, serviceExternal).getProducer();
if (producer != null) {
return ServiceUrlCollectionBean.anyBean(serviceId, serviceProtocol, servicePort, servicePath, serviceEndpoint, serviceExternal, Types.LIST_OF_STRINGS).getProducer().produce(context);
} else {
throw new IllegalStateException("Could not find producer for endpoints of service:" + serviceId + " protocol:" + serviceProtocol);
}
}
}
/**
* Get Service Bean from the context or create a producer.
* @param serviceId
* @param serviceProtocol
* @param context
* @return
*/
private static Object getServiceBean(String serviceId, String serviceProtocol, String servicePort, String servicePath, Boolean serviceExternal, Boolean serviceEndpoint, Type serviceType, CreationalContext context) {
try {
return BeanProvider.getContextualReference(Types.asClass(serviceType), Qualifiers.create(serviceId, serviceProtocol, servicePort, servicePath, serviceEndpoint, serviceExternal));
} catch (IllegalStateException e) {
Producer producer = ServiceBean.anyBean(serviceId, serviceProtocol, servicePort, servicePath, serviceEndpoint, serviceExternal, serviceType).getProducer();
if (producer != null) {
return producer.produce(context);
} else {
throw new IllegalStateException("Could not find producer for service:" + serviceId + " type:" + serviceType + " protocol:" + serviceProtocol);
}
}
}
/**
* Get Configuration from context or create a producer.
* @param serviceId
* @param type
* @param context
* @return
*/
private static Object getConfiguration(String serviceId, Type type, CreationalContext context) {
try {
return BeanProvider.getContextualReference(Types.asClass(type), new ConfigurationQualifier(serviceId));
} catch (IllegalStateException e) {
//Contextual Refernece not found, let's fallback to Configuration Producer.
return ConfigurationBean.getBean(serviceId, type).getProducer().produce(context);
}
}
}