org.glassfish.ejb.deployment.annotation.handlers.AbstractEjbHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of payara-micro Show documentation
Show all versions of payara-micro Show documentation
Micro Distribution of the Payara Project
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
// Portions Copyright [2016-2021] [Payara Foundation and/or its affiliates]
package org.glassfish.ejb.deployment.annotation.handlers;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import jakarta.ejb.EJBHome;
import jakarta.ejb.EJBLocalHome;
import jakarta.ejb.Local;
import jakarta.ejb.LocalBean;
import jakarta.ejb.LocalHome;
import jakarta.ejb.Remote;
import jakarta.ejb.RemoteHome;
import jakarta.ejb.Timeout;
import com.sun.enterprise.deployment.MethodDescriptor;
import com.sun.enterprise.deployment.annotation.context.EjbBundleContext;
import com.sun.enterprise.deployment.annotation.context.EjbContext;
import com.sun.enterprise.deployment.annotation.context.EjbsContext;
import com.sun.enterprise.deployment.annotation.handlers.AbstractHandler;
import com.sun.enterprise.util.LocalStringManagerImpl;
import fish.payara.nucleus.hotdeploy.ApplicationState;
import org.glassfish.apf.AnnotatedElementHandler;
import org.glassfish.apf.AnnotationInfo;
import org.glassfish.apf.AnnotationProcessorException;
import org.glassfish.apf.HandlerProcessingResult;
import org.glassfish.apf.context.AnnotationContext;
import org.glassfish.config.support.TranslatedConfigView;
import org.glassfish.ejb.deployment.descriptor.DummyEjbDescriptor;
import org.glassfish.ejb.deployment.descriptor.EjbBundleDescriptorImpl;
import org.glassfish.ejb.deployment.descriptor.EjbDescriptor;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.internal.api.Globals;
import org.glassfish.internal.deployment.AnnotationTypesProvider;
/**
* This is an abstract class for EJB annotation handler.
* Concrete subclass handlers need to implements the following methods:
* public Class<? extends Annotation> getAnnotationType();
* protected String getAnnotatedName(Annotation annotation );
* protected boolean isValidEjbDescriptor(EjbDescriptor ejbDesc);
* Annotation annotation);
* protected EjbDescriptor createEjbDescriptor(String elementName,
* AnnotationInfo ainfo) throws AnnotationProcessorException;
* protected HandlerProcessingResult setEjbDescriptorInfo(
* EjbDescriptor ejbDesc, AnnotationInfo ainfo)
* throws AnnotationProcessorException;
*
* @author Shing Wai Chan
*/
public abstract class AbstractEjbHandler extends AbstractHandler {
private AnnotationTypesProvider provider = null;
protected final static LocalStringManagerImpl localStrings =
new LocalStringManagerImpl(AbstractEjbHandler.class);
public AbstractEjbHandler() {
ServiceLocator h = Globals.getDefaultHabitat();
if( h != null ) {
provider = h.getService(AnnotationTypesProvider.class, "EJB");
}
}
/**
* Return the name attribute of given annotation.
* @param annotation
* @return name
*/
protected abstract String getAnnotatedName(Annotation annotation);
/*
* check if the given EjbDescriptor matches the given Annotation.
* @param ejbDesc
* @param annotation
* @return boolean check for validity of EjbDescriptor
*/
protected abstract boolean isValidEjbDescriptor(EjbDescriptor ejbDesc,
Annotation annotation);
/**
* Create a new EjbDescriptor for a given elementName and AnnotationInfo.
* @param elementName
* @param ainfo
* @return a new EjbDescriptor
*/
protected abstract EjbDescriptor createEjbDescriptor(String elementName,
AnnotationInfo ainfo) throws AnnotationProcessorException;
/**
* Set Annotation information to Descriptor.
* This method will also be invoked for an existing descriptor with
* annotation as user may not specific a complete xml.
* @param ejbDesc
* @param ainfo
* @return HandlerProcessingResult
*/
protected abstract HandlerProcessingResult setEjbDescriptorInfo(
EjbDescriptor ejbDesc, AnnotationInfo ainfo)
throws AnnotationProcessorException;
/**
* Process a particular annotation which type is the same as the one
* returned by @see getAnnotationType().All information pertinent to the
* annotation and its context is encapsulated in the passed AnnotationInfo
* instance.This is a method in interface AnnotationHandler.
*
* @param ainfo the annotation information
* @return
* @throws org.glassfish.apf.AnnotationProcessorException
*/
@Override
public HandlerProcessingResult processAnnotation(AnnotationInfo ainfo)
throws AnnotationProcessorException {
ApplicationState state = ainfo
.getProcessingContext()
.getArchive()
.getExtraData(ApplicationState.class);
Class ejbClass = (Class) ainfo.getAnnotatedElement();
Annotation annotation = ainfo.getAnnotation();
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER, "@ process ejb annotation {0} in {1}", new Object[]{annotation, ejbClass});
}
AnnotatedElementHandler aeHandler =
ainfo.getProcessingContext().getHandler();
if (aeHandler != null && aeHandler instanceof EjbContext) {
EjbContext context = (EjbContext)aeHandler;
EjbDescriptor desc = (EjbDescriptor) context.getDescriptor();
if (isValidEjbDescriptor(desc, annotation)) {
return getDefaultProcessedResult();
} else {
log(Level.SEVERE, ainfo,
localStrings.getLocalString(
"enterprise.deployment.annotation.handlers.notcompsuperclass",
"The annotation symbol defined in super-class is not compatible with {0} ejb {1}.",
new Object[] { desc.getType(), desc.getName() }));
return getDefaultFailedResult();
}
} else if (aeHandler == null || !(aeHandler instanceof EjbBundleContext)) {
return getInvalidAnnotatedElementHandlerResult(
ainfo.getProcessingContext().getHandler(), ainfo);
}
EjbBundleContext ctx = (EjbBundleContext)aeHandler;
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "My context is {0}", ctx);
}
String elementName = getAnnotatedName(annotation);
if (elementName.length() == 0) {
elementName = ejbClass.getSimpleName();
}
else {
elementName = TranslatedConfigView.expandValue(elementName);
}
EjbBundleDescriptorImpl currentBundle = (EjbBundleDescriptorImpl) ctx.getDescriptor();
EjbDescriptor ejbDesc = null;
try {
ejbDesc = currentBundle.getEjbByName(elementName);
} catch(IllegalArgumentException ex) {
//getEjbByName throws IllegalArgumentException when no ejb is found
}
if(state != null && ejbDesc != null) {
currentBundle.removeEjb(ejbDesc);
ejbDesc = null;
}
if (ejbDesc != null && !(ejbDesc instanceof DummyEjbDescriptor) ) {
// element has already been defined in the standard DDs,
// overriding rules applies
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Overriding rules apply for {0}", ejbClass.getName());
}
// don't allow ejb-jar.xml overwrite ejb type
if (!isValidEjbDescriptor(ejbDesc, annotation)) {
// this is an error
log(Level.SEVERE, ainfo,
localStrings.getLocalString(
"enterprise.deployment.annotation.handlers.wrongejbtype",
"Wrong annotation symbol for ejb {0}",
new Object[] { ejbDesc }));
return getDefaultFailedResult();
}
// is optional if a component-defining
// annotation is used. If present, element
// must match the class on which the component defining annotation
// appears.
String descriptorEjbClass = ejbDesc.getEjbClassName();
if( descriptorEjbClass == null ) {
ejbDesc.setEjbClassName(ejbClass.getName());
ejbDesc.applyDefaultClassToLifecycleMethods();
} else if( !descriptorEjbClass.equals(ejbClass.getName()) ) {
log(Level.SEVERE, ainfo,
localStrings.getLocalString(
"enterprise.deployment.annotation.handlers.ejbclsmismatch",
"",
new Object[] { descriptorEjbClass, elementName,
ejbClass.getName() }));
return getDefaultFailedResult();
}
} else {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Creating a new descriptor for {0}", ejbClass.getName());
}
EjbDescriptor dummyEjbDesc = ejbDesc;
ejbDesc = createEjbDescriptor(elementName, ainfo);
// create the actual ejb descriptor using annotation info and
// the information from dummy ejb descriptor if applicable
if (dummyEjbDesc != null) {
currentBundle.removeEjb(dummyEjbDesc);
ejbDesc.addEjbDescriptor(dummyEjbDesc);
// reset ejbClassName on ejbDesc
ejbDesc.setEjbClassName(ejbClass.getName());
}
// add the actual ejb descriptor to the ejb bundle
currentBundle.addEjb(ejbDesc);
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "New {0} bean {1}", new Object[]{getAnnotationType().getName(), elementName});
}
}
// We need to include all ejbs of the same name in the annotation processing context
// in order to handle the case that a bean class has both a component-defining
// annotation and there are other ejb-jar.xml-defined beans with the same bean class.
EjbDescriptor[] ejbDescs = currentBundle.getEjbByClassName(ejbClass.getName());
HandlerProcessingResult procResult = null;
for(EjbDescriptor next : ejbDescs) {
procResult = setEjbDescriptorInfo(next, ainfo);
doTimedObjectProcessing(ejbClass, next);
}
AnnotationContext annContext = null;
if( ejbDescs.length == 1 ) {
annContext = new EjbContext(ejbDesc, ejbClass);
} else {
annContext = new EjbsContext(ejbDescs, ejbClass);
}
// we push the new context on the stack...
ctx.getProcessingContext().pushHandler(annContext);
return procResult;
}
/**
* Process TimedObject and @Timeout annotation. It's better to do it
* when processing the initial bean type since Timeout method is a
* business method that should be included in any tx processing defaulting
* that takes place.
*/
private void doTimedObjectProcessing(Class ejbClass,
EjbDescriptor ejbDesc) {
// Timeout methods can be declared on the bean class or any
// super-class and can be public, protected, private, or
// package access. There can be at most one timeout method for
// the entire bean class hierarchy, so we start from the bean
// class and go up, stopping when we find the first one.
MethodDescriptor timeoutMethodDesc = null;
Class nextClass = ejbClass;
while((nextClass != Object.class) && (nextClass != null)
&& (timeoutMethodDesc == null) ) {
Method[] methods = nextClass.getDeclaredMethods();
for(Method m : methods) {
if( (m.getAnnotation(Timeout.class) != null) ) {
timeoutMethodDesc =
new MethodDescriptor(m, MethodDescriptor.TIMER_METHOD);
break;
}
}
nextClass = nextClass.getSuperclass();
}
if( (timeoutMethodDesc == null) &&
jakarta.ejb.TimedObject.class.isAssignableFrom(ejbClass) ) {
// If the class implements the TimedObject interface, it must
// be ejbTimeout.
timeoutMethodDesc = new MethodDescriptor
("ejbTimeout", "@Timeout method",
new String[] { "jakarta.ejb.Timer" },
MethodDescriptor.TIMER_METHOD);
}
if( timeoutMethodDesc != null ) {
ejbDesc.setEjbTimeoutMethod(timeoutMethodDesc);
}
}
/**
* MessageDriven bean does not need to invoke this API.
* @param ejbDesc
* @param ainfo for error handling
* @return HandlerProcessingResult
*/
protected HandlerProcessingResult setBusinessAndHomeInterfaces(
EjbDescriptor ejbDesc, AnnotationInfo ainfo)
throws AnnotationProcessorException {
Set localBusIntfs = new HashSet();
Set remoteBusIntfs = new HashSet();
Set clientInterfaces = new HashSet();
Class ejbClass = (Class)ainfo.getAnnotatedElement();
// First check for annotations specifying remote/local business intfs.
// We analyze them here because they are needed during the
// implements clause processing for beans that specify
// @Stateless/@Stateful. In addition, they should *not* be processed
// if there is no @Stateful/@Stateless annotation.
Remote remoteBusAnn = (Remote) ejbClass.getAnnotation(Remote.class);
boolean emptyRemoteBusAnn = false;
if( remoteBusAnn != null ) {
for(Class next : remoteBusAnn.value()) {
if (next.getAnnotation(Local.class) != null) {
AnnotationProcessorException fatalException =
new AnnotationProcessorException(localStrings.getLocalString(
"enterprise.deployment.annotation.handlers.invalidbusinessinterface",
"The interface {0} cannot be both a local and a remote business interface.",
new Object[]{next.getName()}));
fatalException.setFatal(true);
throw fatalException;
}
clientInterfaces.add(next);
remoteBusIntfs.add(next);
}
emptyRemoteBusAnn = remoteBusIntfs.isEmpty();
}
Local localBusAnn = (Local) ejbClass.getAnnotation(Local.class);
if( localBusAnn != null ) {
for(Class next : localBusAnn.value()) {
if (next.getAnnotation(Remote.class) != null) {
AnnotationProcessorException fatalException =
new AnnotationProcessorException(localStrings.getLocalString(
"enterprise.deployment.annotation.handlers.invalidbusinessinterface",
"The interface {0} cannot be both a local and a remote business interface.",
new Object[]{next.getName()}));
fatalException.setFatal(true);
throw fatalException;
}
clientInterfaces.add(next);
localBusIntfs.add(next);
}
}
List imlementingInterfaces = new ArrayList();
List implementedDesignatedInterfaces = new ArrayList();
for(Class next : ejbClass.getInterfaces()) {
if( !excludedFromImplementsClause(next) ) {
if( next.getAnnotation(Local.class) != null || next.getAnnotation(Remote.class) != null ) {
implementedDesignatedInterfaces.add(next);
}
imlementingInterfaces.add(next);
}
}
LocalBean localBeanAnn = (LocalBean) ejbClass.getAnnotation(LocalBean.class);
if( localBeanAnn != null ) {
ejbDesc.setLocalBean(true);
}
// total number of local/remote business interfaces declared
// outside of the implements clause plus implemented designated interfaces
int designatedInterfaceCount =
remoteBusIntfs.size() + localBusIntfs.size() +
ejbDesc.getRemoteBusinessClassNames().size() +
ejbDesc.getLocalBusinessClassNames().size() +
implementedDesignatedInterfaces.size();
for(Class next : imlementingInterfaces) {
String nextIntfName = next.getName();
if( remoteBusIntfs.contains(next)
||
localBusIntfs.contains(next)
||
ejbDesc.getRemoteBusinessClassNames().contains(nextIntfName)
||
ejbDesc.getLocalBusinessClassNames().contains(nextIntfName)){
// Interface has already been identified as a Remote/Local
// business interface, so ignore.
} else if( next.getAnnotation(Local.class) != null ) {
clientInterfaces.add(next);
localBusIntfs.add(next);
} else if( next.getAnnotation(Remote.class) != null ) {
clientInterfaces.add(next);
remoteBusIntfs.add(next);
} else {
if( (designatedInterfaceCount == 0) &&
(!ejbDesc.isLocalBean()) ) {
// If there's an empty @Remote annotation on the class,
// it's treated as a remote business interface. Otherwise,
// it's treated as a local business interface.
if( emptyRemoteBusAnn ) {
remoteBusIntfs.add(next);
} else {
localBusIntfs.add(next);
}
clientInterfaces.add(next);
} else {
// Since the component has at least one other business
// interface, each implements clause interface that cannot
// be identified as business interface via the deployment
// descriptor or a @Remote/@Local annotation is ignored.
}
}
}
for (Class next : clientInterfaces) {
if (remoteBusIntfs.contains(next) && localBusIntfs.contains(next)) {
AnnotationProcessorException fatalException =
new AnnotationProcessorException(localStrings.getLocalString(
"enterprise.deployment.annotation.handlers.invalidbusinessinterface",
"The interface {0} cannot be both a local and a remote business interface.",
new Object[]{next.getName()}));
fatalException.setFatal(true);
throw fatalException;
}
}
if (localBusIntfs.size() > 0) {
for(Class next : localBusIntfs) {
ejbDesc.addLocalBusinessClassName(next.getName());
}
}
if (remoteBusIntfs.size() > 0) {
for(Class next : remoteBusIntfs) {
ejbDesc.addRemoteBusinessClassName(next.getName());
}
}
// Do Adapted @Home / Adapted @LocalHome processing here too since
// they are logically part of the structural @Stateless/@Stateful info.
RemoteHome remoteHomeAnn = (RemoteHome)
ejbClass.getAnnotation(RemoteHome.class);
if( remoteHomeAnn != null ) {
Class remoteHome = remoteHomeAnn.value();
Class remoteIntf = getComponentIntfFromHome(remoteHome);
if( EJBHome.class.isAssignableFrom(remoteHome) &&
(remoteIntf != null) ) {
clientInterfaces.add(remoteHome);
ejbDesc.setHomeClassName(remoteHome.getName());
ejbDesc.setRemoteClassName(remoteIntf.getName());
} else {
log(Level.SEVERE, ainfo,
localStrings.getLocalString(
"enterprise.deployment.annotation.handlers.invalidremotehome",
"Encountered invalid @RemoteHome interface {0}.",
new Object[] { remoteHome }));
return getDefaultFailedResult();
}
}
LocalHome localHomeAnn = (LocalHome)
ejbClass.getAnnotation(LocalHome.class);
if( localHomeAnn != null ) {
Class localHome = localHomeAnn.value();
Class localIntf = getComponentIntfFromHome(localHome);
if( EJBLocalHome.class.isAssignableFrom(localHome) &&
(localIntf != null) ) {
clientInterfaces.add(localHome);
ejbDesc.setLocalHomeClassName(localHome.getName());
ejbDesc.setLocalClassName(localIntf.getName());
} else {
log(Level.SEVERE, ainfo,
localStrings.getLocalString(
"enterprise.deployment.annotation.handlers.invalidlocalhome",
"Encountered invalid @LocalHome interface {0}.",
new Object[] { localHome }));
return getDefaultFailedResult();
}
}
// Web Service API might not be available so do a check before looking
// for @WebService on bean class
boolean canDoWebServiceAnnCheck = false;
try {
canDoWebServiceAnnCheck = (provider.getType("jakarta.jws.WebService") != null);
} catch(Exception e) {
log(Level.FINE, ainfo, e.getMessage());
}
if( (!ejbDesc.isLocalBean()) &&
(clientInterfaces.size() == 0) &&
!ejbDesc.hasWebServiceEndpointInterface() &&
( !canDoWebServiceAnnCheck ||
(ejbClass.getAnnotation(jakarta.jws.WebService.class) == null) ) ) {
ejbDesc.setLocalBean(true);
}
//If this is a no-Interface local EJB, set all classes for this bean
if (ejbDesc.isLocalBean()) {
addNoInterfaceLocalBeanClasses(ejbDesc, ejbClass);
}
return getDefaultProcessedResult();
}
private void addNoInterfaceLocalBeanClasses(EjbDescriptor ejbDesc, Class ejbClass) {
Class nextClass = ejbClass;
//The session bean's no-interface view can be accessed via the bean class
//and all its super classes
while ((nextClass != Object.class) && (nextClass != null)) {
ejbDesc.addNoInterfaceLocalBeanClass(nextClass.getName());
nextClass = nextClass.getSuperclass();
}
}
private Class getComponentIntfFromHome(Class homeIntf) {
Class componentIntf = null;
Method[] methods = homeIntf.getMethods();
for (Method m : methods) {
if (m.getName().startsWith("create")) {
componentIntf = m.getReturnType();
break;
}
}
return componentIntf;
}
protected boolean excludedFromImplementsClause(Class intf) {
return ( (intf == java.io.Serializable.class) ||
(intf == java.io.Externalizable.class) ||
( (intf.getPackage() != null) &&
intf.getPackage().getName().equals("jakarta.ejb")) );
}
protected void doDescriptionProcessing(String description,
EjbDescriptor ejbDescriptor) {
// Since there are multiple descriptions allowed in the deployment
// descriptor, there are no overriding issues here. If the
// component-defining annotation contains a description, it will
// always be added to the list of descriptions for the bean.
if( (description != null) && !description.equals("") ) {
ejbDescriptor.setDescription(description);
}
}
protected void doMappedNameProcessing(String mappedName,
EjbDescriptor ejbDesc) {
// Set mappedName() if a value has been given in the annotation and
// it hasn't already been set on the descriptor via the .xml.
if( ejbDesc.getMappedName().equals("") ) {
if( !mappedName.equals("") ) {
ejbDesc.setMappedName(mappedName);
}
}
}
}