/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.ejb3.deployment.processors;
import org.jboss.as.ee.component.Attachments;
import org.jboss.as.ee.component.BindingConfiguration;
import org.jboss.as.ee.component.EEModuleClassDescription;
import org.jboss.as.ee.component.EEModuleDescription;
import org.jboss.as.ee.component.FieldInjectionTarget;
import org.jboss.as.ee.component.InjectionSource;
import org.jboss.as.ee.component.InjectionTarget;
import org.jboss.as.ee.component.LookupInjectionSource;
import org.jboss.as.ee.component.MethodInjectionTarget;
import org.jboss.as.ee.component.ResourceInjectionConfiguration;
import org.jboss.as.ee.structure.EJBAnnotationPropertyReplacement;
import org.jboss.as.ejb3.logging.EjbLogger;
import org.jboss.as.ejb3.deployment.EjbDeploymentAttachmentKeys;
import org.jboss.as.server.deployment.DeploymentPhaseContext;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
import org.jboss.as.server.deployment.DeploymentUnitProcessor;
import org.jboss.as.server.deployment.annotation.CompositeIndex;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.FieldInfo;
import org.jboss.jandex.MethodInfo;
import org.jboss.metadata.property.PropertyReplacer;
import javax.ejb.EJB;
import javax.ejb.EJBs;
import java.util.List;
import java.util.Locale;
/**
* Deployment processor responsible for processing @EJB annotations within components. Each @EJB annotation will be registered
* as an injection binding for the component.
*
* @author John Bailey
* @author <a href="mailto:ropalka@redhat.com">Richard Opalka</a>
*/
public class EjbResourceInjectionAnnotationProcessor implements DeploymentUnitProcessor {
private static final DotName EJB_ANNOTATION_NAME = DotName.createSimple(EJB.class.getName());
private static final DotName EJBS_ANNOTATION_NAME = DotName.createSimple(EJBs.class.getName());
private final boolean appclient;
public EjbResourceInjectionAnnotationProcessor(final boolean appclient) {
this.appclient = appclient;
}
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
final EEModuleDescription moduleDescription = deploymentUnit.getAttachment(Attachments.EE_MODULE_DESCRIPTION);
final CompositeIndex index = deploymentUnit.getAttachment(org.jboss.as.server.deployment.Attachments.COMPOSITE_ANNOTATION_INDEX);
final List<AnnotationInstance> resourceAnnotations = index.getAnnotations(EJB_ANNOTATION_NAME);
PropertyReplacer propertyReplacer = EJBAnnotationPropertyReplacement.propertyReplacer(deploymentUnit);
for (AnnotationInstance annotation : resourceAnnotations) {
final AnnotationTarget annotationTarget = annotation.target();
final EJBResourceWrapper annotationWrapper = new EJBResourceWrapper(annotation, propertyReplacer);
if (annotationTarget instanceof FieldInfo) {
processField(deploymentUnit, annotationWrapper, (FieldInfo) annotationTarget, moduleDescription);
} else if (annotationTarget instanceof MethodInfo) {
processMethod(deploymentUnit, annotationWrapper, (MethodInfo) annotationTarget, moduleDescription);
} else if (annotationTarget instanceof ClassInfo) {
processClass(deploymentUnit, annotationWrapper, (ClassInfo) annotationTarget, moduleDescription);
}
}
final List<AnnotationInstance> ejbsAnnotations = index.getAnnotations(EJBS_ANNOTATION_NAME);
for (AnnotationInstance annotation : ejbsAnnotations) {
final AnnotationTarget annotationTarget = annotation.target();
if (annotationTarget instanceof ClassInfo) {
final AnnotationValue annotationValue = annotation.value();
final AnnotationInstance[] ejbAnnotations = annotationValue.asNestedArray();
for (AnnotationInstance ejbAnnotation : ejbAnnotations) {
final EJBResourceWrapper annotationWrapper = new EJBResourceWrapper(ejbAnnotation, propertyReplacer);
processClass(deploymentUnit, annotationWrapper, (ClassInfo) annotationTarget, moduleDescription);
}
} else {
throw EjbLogger.ROOT_LOGGER.annotationOnlyAllowedOnClass(EJBs.class.getName(), annotation.target());
}
}
}
public void undeploy(DeploymentUnit context) {
}
private void processField(final DeploymentUnit deploymentUnit, final EJBResourceWrapper annotation, final FieldInfo fieldInfo, final EEModuleDescription eeModuleDescription) {
final String fieldName = fieldInfo.name();
final String fieldType = fieldInfo.type().name().toString();
final InjectionTarget targetDescription = new FieldInjectionTarget(fieldInfo.declaringClass().name().toString(), fieldName, fieldType);
final String localContextName = isEmpty(annotation.name()) ? fieldInfo.declaringClass().name().toString() + "/" + fieldInfo.name() : annotation.name();
final String beanInterfaceType = isEmpty(annotation.beanInterface()) || annotation.beanInterface().equals(Object.class.getName()) ? fieldType : annotation.beanInterface();
process(deploymentUnit, beanInterfaceType, annotation.beanName(), annotation.lookup(), fieldInfo.declaringClass(), targetDescription, localContextName, eeModuleDescription);
}
private void processMethod(final DeploymentUnit deploymentUnit, final EJBResourceWrapper annotation, final MethodInfo methodInfo, final EEModuleDescription eeModuleDescription) {
final String methodName = methodInfo.name();
if (!methodName.startsWith("set") || methodInfo.args().length != 1) {
throw EjbLogger.ROOT_LOGGER.onlySetterMethodsAllowedToHaveEJBAnnotation(methodInfo);
}
final String methodParamType = methodInfo.args()[0].name().toString();
final InjectionTarget targetDescription = new MethodInjectionTarget(methodInfo.declaringClass().name().toString(), methodName, methodParamType);
final String localContextName = isEmpty(annotation.name()) ? methodInfo.declaringClass().name().toString() + "/" + methodName.substring(3, 4).toLowerCase(Locale.ENGLISH) + methodName.substring(4) : annotation.name();
final String beanInterfaceType = isEmpty(annotation.beanInterface()) || annotation.beanInterface().equals(Object.class.getName()) ? methodParamType : annotation.beanInterface();
process(deploymentUnit, beanInterfaceType, annotation.beanName(), annotation.lookup(), methodInfo.declaringClass(), targetDescription, localContextName, eeModuleDescription);
}
private void processClass(final DeploymentUnit deploymentUnit, final EJBResourceWrapper annotation, final ClassInfo classInfo, final EEModuleDescription eeModuleDescription) throws DeploymentUnitProcessingException {
if (isEmpty(annotation.name())) {
throw EjbLogger.ROOT_LOGGER.nameAttributeRequiredForEJBAnnotationOnClass(classInfo.toString());
}
if (isEmpty(annotation.beanInterface())) {
throw EjbLogger.ROOT_LOGGER.beanInterfaceAttributeRequiredForEJBAnnotationOnClass(classInfo.toString());
}
process(deploymentUnit, annotation.beanInterface(), annotation.beanName(), annotation.lookup(), classInfo, null, annotation.name(), eeModuleDescription);
}
private void process(final DeploymentUnit deploymentUnit, final String beanInterface, final String beanName, final String lookup, final ClassInfo classInfo, final InjectionTarget targetDescription, final String localContextName, final EEModuleDescription eeModuleDescription) {
if (!isEmpty(lookup) && !isEmpty(beanName)) {
EjbLogger.DEPLOYMENT_LOGGER.debugf("Both beanName = %s and lookup = %s have been specified in @EJB annotation. Lookup will be given preference. Class: %s"
, beanName, lookup, classInfo.name());
}
final EEModuleClassDescription classDescription = eeModuleDescription.addOrGetLocalClassDescription(classInfo.name().toString());
final InjectionSource valueSource;
EjbInjectionSource ejbInjectionSource = null;
//give preference to lookup
if (!isEmpty(lookup)) {
if (!lookup.startsWith("java:")) {
valueSource = new EjbLookupInjectionSource(lookup, targetDescription == null ? null : targetDescription.getDeclaredValueClassName());
} else {
valueSource = createLookup(lookup, appclient);
}
} else if (!isEmpty(beanName)) {
valueSource = ejbInjectionSource = new EjbInjectionSource(beanName, beanInterface, localContextName, deploymentUnit, appclient);
} else {
valueSource = ejbInjectionSource = new EjbInjectionSource(beanInterface, localContextName, deploymentUnit, appclient);
}
if (ejbInjectionSource != null) {
deploymentUnit.addToAttachmentList(EjbDeploymentAttachmentKeys.EJB_INJECTIONS, ejbInjectionSource);
}
// our injection comes from the local lookup, no matter what.
final ResourceInjectionConfiguration injectionConfiguration = targetDescription != null ?
new ResourceInjectionConfiguration(targetDescription, createLookup(localContextName, appclient)) : null;
// Create the binding from whence our injection comes.
final BindingConfiguration bindingConfiguration = new BindingConfiguration(localContextName, valueSource);
classDescription.getBindingConfigurations().add(bindingConfiguration);
if (injectionConfiguration != null) {
classDescription.addResourceInjection(injectionConfiguration);
}
}
private InjectionSource createLookup(final String localContextName, final boolean appclient) {
//appclient lookups are always optional
//as they could reference local interfaces that are not present
return new LookupInjectionSource(localContextName, appclient);
}
private boolean isEmpty(final String string) {
return string == null || string.isEmpty();
}
private class EJBResourceWrapper {
private final String name;
private final String beanInterface;
private final String beanName;
private final String lookup;
private final PropertyReplacer propertyReplacer;
public EJBResourceWrapper(AnnotationInstance annotation, PropertyReplacer propertyReplacer) {
this.propertyReplacer = propertyReplacer;
name = stringValueOrNull(annotation, "name");
beanInterface = classValueOrNull(annotation, "beanInterface");
beanName = stringValueOrNull(annotation, "beanName");
String lookupValue = stringValueOrNull(annotation, "lookup");
// if "lookup" isn't specified, then fallback on "mappedName". We treat "mappedName" the same as "lookup"
if (isEmpty(lookupValue)) {
lookupValue = stringValueOrNull(annotation, "mappedName");
}
this.lookup = lookupValue;
}
private String name() {
return name;
}
private String beanInterface() {
return beanInterface;
}
private String beanName() {
return beanName;
}
private String lookup() {
return lookup;
}
private String stringValueOrNull(final AnnotationInstance annotation, final String attribute) {
final AnnotationValue value = annotation.value(attribute);
return (value != null) ? propertyReplacer.replaceProperties(value.asString()) : null;
}
private String classValueOrNull(final AnnotationInstance annotation, final String attribute) {
final AnnotationValue value = annotation.value(attribute);
return (value != null) ? value.asClass().name().toString() : null;
}
}
}