org.jboss.ejb.client.EJBInvocationHandler Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote Jakarta Enterprise Beans and Jakarta Messaging, including
all dependencies. It is intended for use by those not using maven, maven users should just import the Jakarta Enterprise Beans and
Jakarta Messaging BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
/*
* 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.ejb.client;
import java.io.NotSerializableException;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.rmi.RemoteException;
import java.rmi.UnmarshalException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Future;
import javax.ejb.EJBException;
import javax.ejb.EJBHome;
import javax.ejb.EJBObject;
import org.jboss.ejb.client.annotation.CompressionHint;
/**
* @param the proxy view type
* @author David M. Lloyd
*/
@SuppressWarnings({"SerializableClassWithUnconstructableAncestor"})
final class EJBInvocationHandler extends Attachable implements InvocationHandler, Serializable {
private static final long serialVersionUID = 946555285095057230L;
private static final String ENABLE_ANNOTATION_SCANNING_SYSTEM_PROPERTY = "org.jboss.ejb.client.view.annotation.scan.enabled";
private final transient boolean async;
/**
* @serial the associated EJB locator
*/
private final EJBLocator locator;
/**
* @serial the weak affinity
*/
private volatile Affinity weakAffinity = Affinity.NONE;
/**
* An optional association to a EJB client context
*/
// we don't serialize EJB client context identifier
private final transient EJBClientContextIdentifier ejbClientContextIdentifier;
/**
* The {@link AnnotationScanner}s which will be used for scanning annotations on the view class which corresponds to the proxy represented by this invocation handler
*/
// The whole annotation scanning business is for now contained within this invocation handler class and isn't exposed or isn't allowed to be "pluggable" to avoid unnecesary complexity
// in the EJB client API. The only control user applications have over annotation scanning of view class(es), for now, is via setting a system property to enable or disable the scanning.
private final transient AnnotationScanner[] annotationScanners = new AnnotationScanner[]{new CompressionHintAnnotationScanner()};
/**
* map of methods that can be handled on the client side
*/
private static final Map clientSideMethods;
static {
Map methods = new HashMap();
methods.put(new MethodKey("equals", Object.class), new EqualsMethodHandler());
methods.put(new MethodKey("hashCode"), new HashCodeMethodHandler());
methods.put(new MethodKey("toString"), new ToStringMethodHandler());
methods.put(new MethodKey("getPrimaryKey"), new GetPrimaryKeyHandler());
methods.put(new MethodKey("getHandle"), new GetHandleHandler());
methods.put(new MethodKey("isIdentical", EJBObject.class), new IsIdenticalHandler());
methods.put(new MethodKey("getHomeHandle"), new GetHomeHandleHandler());
clientSideMethods = Collections.unmodifiableMap(methods);
}
EJBInvocationHandler(final EJBLocator locator) {
this(null, locator);
}
/**
* Creates an {@link EJBInvocationHandler} for the passed locator
and associates this
* invocation handler with the passed ejbClientContextIdentifier
*
* @param ejbClientContextIdentifier (Optional) EJB client context identifier. Can be null.
* @param locator The {@link EJBLocator} cannot be null.
*/
EJBInvocationHandler(final EJBClientContextIdentifier ejbClientContextIdentifier, final EJBLocator locator) {
if (locator == null) {
throw Logs.MAIN.paramCannotBeNull("EJB locator");
}
this.ejbClientContextIdentifier = ejbClientContextIdentifier;
this.locator = locator;
async = false;
if (locator instanceof StatefulEJBLocator) {
// set the weak affinity to the node on which the session was created
final String sessionOwnerNode = ((StatefulEJBLocator>) locator).getSessionOwnerNode();
if (sessionOwnerNode != null) {
this.setWeakAffinity(new NodeAffinity(sessionOwnerNode));
}
}
// scan for annotations on the view class, if necessary
final String annotationScanEnabledSysPropVal = SecurityActions.getSystemProperty(ENABLE_ANNOTATION_SCANNING_SYSTEM_PROPERTY);
if (annotationScanEnabledSysPropVal != null && Boolean.valueOf(annotationScanEnabledSysPropVal.trim())) {
scanAnnotationsOnViewClass();
} else {
// let's for the sake of potential performance optimization, add an attachment which lets the EJBReceiver(s) and any other decision making
// code to decide whether it can entirely skip any logic related to "hints" (like @org.jboss.ejb.client.annotation.CompressionHint)
putAttachment(AttachmentKeys.HINTS_DISABLED, true);
}
}
EJBInvocationHandler(final EJBInvocationHandler other) {
super(other);
locator = other.locator;
async = true;
ejbClientContextIdentifier = other.ejbClientContextIdentifier;
if (locator instanceof StatefulEJBLocator) {
// set the weak affinity to the node on which the session was created
final String sessionOwnerNode = ((StatefulEJBLocator>) locator).getSessionOwnerNode();
if (sessionOwnerNode != null) {
this.setWeakAffinity(new NodeAffinity(sessionOwnerNode));
}
}
}
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
return doInvoke(locator.getViewType().cast(proxy), method, args);
}
void setWeakAffinity(Affinity newWeakAffinity) {
weakAffinity = newWeakAffinity;
}
Affinity getWeakAffinity() {
return weakAffinity;
}
/**
* Returns the {@link EJBClientContextIdentifier} associated with this invocation handler. If this
* invocation handler isn't associated with any {@link EJBClientContextIdentifier} then this method
* returns null
*
* @return
*/
EJBClientContextIdentifier getEjbClientContextIdentifier() {
return this.ejbClientContextIdentifier;
}
Object doInvoke(final T proxy, final Method method, final Object[] args) throws Throwable {
final MethodHandler handler = clientSideMethods.get(new MethodKey(method));
if (handler != null && handler.canHandleInvocation(this, proxy, method, args)) {
return handler.invoke(this, proxy, method, args);
}
final EJBClientContext context;
// check if we are associated with a EJB client context identifier
if (ejbClientContextIdentifier == null) {
// we aren't associated with a EJB client context identifier, so select the "current"
// EJB client context
context = EJBClientContext.requireCurrent();
} else {
// we are associated with a specific EJB client context, so fetch it
context = EJBClientContext.require(this.ejbClientContextIdentifier);
}
return doInvoke(this, async, proxy, method, args, context);
}
@SuppressWarnings("unchecked")
static EJBInvocationHandler extends T> forProxy(T proxy) {
InvocationHandler handler = Proxy.getInvocationHandler(proxy);
if (handler instanceof EJBInvocationHandler) {
return (EJBInvocationHandler extends T>) handler;
}
throw Logs.MAIN.proxyNotOurs(proxy, EJBClient.class.getName());
}
private static Object doInvoke(final EJBInvocationHandler ejbInvocationHandler, final boolean async, final T proxy, final Method method, final Object[] args, EJBClientContext clientContext) throws Throwable {
final EJBClientInvocationContext invocationContext = new EJBClientInvocationContext(ejbInvocationHandler, clientContext, proxy, method, args);
try {
// send the request
sendRequestWithPossibleRetries(invocationContext, true);
if (!async) {
// wait for invocation to complete
final Object value = invocationContext.awaitResponse();
if (value != EJBClientInvocationContext.PROCEED_ASYNC) {
return value;
}
// proceed asynchronously
}
// force async...
if (method.getReturnType() == Future.class) {
return invocationContext.getFutureResponse();
} else if (method.getReturnType() == void.class) {
invocationContext.setDiscardResult();
// Void return
return null;
} else {
// wrap return always
EJBClient.setFutureResult(invocationContext.getFutureResponse());
return null;
}
} catch (Exception e) {
//AS7-5937 prevent UndeclaredThrowableException
if (e instanceof RuntimeException) {
throw e;
}
boolean remoteException = false;
for (Class> exception : method.getExceptionTypes()) {
if (exception.isAssignableFrom(e.getClass())) {
throw e;
} else if (RemoteException.class.equals(exception)) {
remoteException = true;
}
}
if (remoteException) {
throw new RemoteException("Error", e);
}
throw new EJBException(e);
}
}
/**
* Sends a method invocation request to an eligible EJB receiver. If the request sending fails with a {@link RequestSendFailedException} then this method attempts to
* retry sending that request to some other eligible node (if any). It does this till either the request was successfully sent or till there are no more eligible EJB receivers
* which can handle this request
*
* @param clientInvocationContext The EJB client invocation context
* @param firstAttempt True if this is a first attempt at sending the request, false if this is a retry
* @throws Exception
*/
private static void sendRequestWithPossibleRetries(final EJBClientInvocationContext clientInvocationContext, final boolean firstAttempt) throws Exception {
try {
// this is the first attempt so use the sendRequest API
if (firstAttempt) {
clientInvocationContext.sendRequest();
} else {
// retry
clientInvocationContext.retryRequest();
}
} catch (RequestSendFailedException rsfe) {
// check whether the first attempt fails during serialization
if (firstAttempt) {
// EJBCLIENT-106 supress retry and throw the root cause
if (rsfe.getCause() instanceof RuntimeException) {
throw (RuntimeException) rsfe.getCause();
}else if(rsfe.getCause() instanceof NotSerializableException) {
throw (NotSerializableException)rsfe.getCause();
}else if (rsfe.getCause() instanceof UnmarshalException) {
throw (UnmarshalException) rsfe.getCause();
}
}
final String failedNodeName = rsfe.getFailedNodeName();
if (failedNodeName != null) {
Logs.MAIN.debugf(rsfe, "Retrying invocation %s which failed on node: %s due to:", clientInvocationContext, failedNodeName);
// exclude this failed node, during the retry
clientInvocationContext.markNodeAsExcluded(failedNodeName);
// retry
sendRequestWithPossibleRetries(clientInvocationContext, false);
} else {
throw rsfe;
}
}
}
@SuppressWarnings("unused")
protected Object writeReplace() {
return new SerializedEJBInvocationHandler(locator);
}
EJBInvocationHandler getAsyncHandler() {
return async ? this : new EJBInvocationHandler(this);
}
boolean isAsyncHandler() {
return this.async;
}
EJBLocator getLocator() {
return locator;
}
/**
* Scans for annotations on the view class and its methods using pre-configured {@link AnnotationScanner}s (if any)
*/
private void scanAnnotationsOnViewClass() {
// nothing to do
if (annotationScanners == null || annotationScanners.length == 0) {
return;
}
final Class> viewClass = this.locator.getViewType();
final Method[] viewMethods = viewClass.getMethods();
for (final AnnotationScanner annotationScanner : annotationScanners) {
// scan class level annotations
annotationScanner.scanClass(viewClass);
// now method level annotations
for (final Method viewMethod : viewMethods) {
annotationScanner.scanMethod(viewMethod);
}
}
}
private static final class MethodKey {
private final String name;
private final Class>[] parameters;
public MethodKey(final String name, final Class>... parameters) {
this.name = name;
this.parameters = parameters;
}
public MethodKey(final Method method) {
this.name = method.getName();
this.parameters = method.getParameterTypes();
}
@Override
public boolean equals(final Object o) {
//we don't care about the return type
//we want covariant methods to still be handled
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MethodKey methodKey = (MethodKey) o;
if (!name.equals(methodKey.name)) return false;
if (!Arrays.equals(parameters, methodKey.parameters)) return false;
return true;
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + Arrays.hashCode(parameters);
return result;
}
}
private interface MethodHandler {
boolean canHandleInvocation(final EJBInvocationHandler> thisHandler, final Object proxy, final Method method, final Object[] args) throws Exception;
Object invoke(final EJBInvocationHandler> thisHandler, final Object proxy, final Method method, final Object[] args) throws Exception;
}
private static final class EqualsMethodHandler implements MethodHandler {
@Override
public boolean canHandleInvocation(final EJBInvocationHandler> thisHandler, final Object proxy, final Method method, final Object[] args) {
return true;
}
@Override
public Object invoke(final EJBInvocationHandler> thisHandler, final Object proxy, final Method method, final Object[] args) {
final Object other = args[0];
if (other instanceof Proxy) {
final InvocationHandler handler = Proxy.getInvocationHandler(other);
if (handler instanceof EJBInvocationHandler) {
return thisHandler.locator.equals(((EJBInvocationHandler>) handler).locator);
}
}
return Boolean.FALSE;
}
}
private static final class HashCodeMethodHandler implements MethodHandler {
@Override
public boolean canHandleInvocation(final EJBInvocationHandler> thisHandler, final Object proxy, final Method method, final Object[] args) {
return true;
}
@Override
public Object invoke(final EJBInvocationHandler> thisHandler, final Object proxy, final Method method, final Object[] args) {
return thisHandler.locator.hashCode();
}
}
private static final class ToStringMethodHandler implements MethodHandler {
@Override
public boolean canHandleInvocation(final EJBInvocationHandler> thisHandler, final Object proxy, final Method method, final Object[] args) {
return true;
}
@Override
public Object invoke(final EJBInvocationHandler> thisHandler, final Object proxy, final Method method, final Object[] args) {
return String.format("Proxy for remote EJB %s", thisHandler.locator);
}
}
private static final class GetPrimaryKeyHandler implements MethodHandler {
@Override
public boolean canHandleInvocation(final EJBInvocationHandler> thisHandler, final Object proxy, final Method method, final Object[] args) throws Exception {
return proxy instanceof EJBObject;
}
@Override
public Object invoke(final EJBInvocationHandler> thisHandler, final Object proxy, final Method method, final Object[] args) throws Exception {
final EJBLocator> locator = thisHandler.locator;
if (locator instanceof EntityEJBLocator) {
return ((EntityEJBLocator) locator).getPrimaryKey();
}
throw new RemoteException("Cannot invoke getPrimaryKey() om " + proxy);
}
}
private static final class GetHandleHandler implements MethodHandler {
@Override
public boolean canHandleInvocation(final EJBInvocationHandler> thisHandler, final Object proxy, final Method method, final Object[] args) throws Exception {
return proxy instanceof EJBObject && thisHandler.locator instanceof EJBLocator;
}
@Override
public Object invoke(final EJBInvocationHandler> thisHandler, final Object proxy, final Method method, final Object[] args) throws Exception {
final EJBLocator locator = (EJBLocator) thisHandler.getLocator();
return new EJBHandle(locator);
}
}
private static final class IsIdenticalHandler implements MethodHandler {
@Override
public boolean canHandleInvocation(final EJBInvocationHandler> thisHandler, final Object proxy, final Method method, final Object[] args) throws Exception {
return proxy instanceof EJBObject && thisHandler.locator instanceof EJBLocator;
}
@Override
public Object invoke(final EJBInvocationHandler> thisHandler, final Object proxy, final Method method, final Object[] args) throws Exception {
final EJBLocator> locator = thisHandler.locator;
final Object other = args[0];
if (Proxy.isProxyClass(other.getClass())) {
final InvocationHandler handler = Proxy.getInvocationHandler(other);
if (handler instanceof EJBInvocationHandler) {
return locator.equals(((EJBInvocationHandler>) handler).getLocator());
}
}
return false;
}
}
private static final class GetHomeHandleHandler implements MethodHandler {
@Override
public boolean canHandleInvocation(final EJBInvocationHandler> thisHandler, final Object proxy, final Method method, final Object[] args) throws Exception {
return proxy instanceof EJBHome;
}
@Override
public Object invoke(final EJBInvocationHandler> thisHandler, final Object proxy, final Method method, final Object[] args) throws Exception {
final EJBLocator locator = (EJBLocator) thisHandler.getLocator();
if (locator instanceof EJBHomeLocator) {
return new EJBHomeHandle((EJBHomeLocator) locator);
}
throw new RemoteException("Cannot invoke getHomeHandle() on " + proxy);
}
}
/**
* An {@link AnnotationScanner} is responsible for scanning for (some known) annotations on the view class and its methods. It's up to the implementations of this
* interface to decide what to do with the annotation it finds on the view class or the view method. Typical implementations attach the found annotation(s) as "attachments" to the
* {@link EJBInvocationHandler} so that the annotation information is available for invocations, happening on this invocation handler, through the use of {@link EJBClientInvocationContext#getProxyAttachment(AttachmentKey)}
*/
private interface AnnotationScanner {
void scanClass(final Class> view);
void scanMethod(final Method method);
}
/**
* An {@link AnnotationScanner} responsible for scanning the {@link org.jboss.ejb.client.annotation.CompressionHint} annotation on the view class and its methods. This
* {@link org.jboss.ejb.client.EJBInvocationHandler.CompressionHintAnnotationScanner} attaches the found annotations (if any) to the invocation handler through the use of {@link EJBInvocationHandler#putAttachment(AttachmentKey, Object)}
* method. The class level {@link org.jboss.ejb.client.annotation.CompressionHint} annotation (if any) is attached with {@link AttachmentKeys#VIEW_CLASS_DATA_COMPRESSION_HINT_ATTACHMENT_KEY} as the key and the method level
* {@link org.jboss.ejb.client.annotation.CompressionHint} annotations (if any) is attached with {@link AttachmentKeys#VIEW_METHOD_DATA_COMPRESSION_HINT_ATTACHMENT_KEY} as the key
*/
private final class CompressionHintAnnotationScanner implements AnnotationScanner {
@Override
public void scanClass(Class> view) {
final CompressionHint compressionHint = (CompressionHint) view.getAnnotation(CompressionHint.class);
// nothing to do
if (compressionHint == null) {
return;
}
// add it as an attachment to the invocation handler so that it's available during an invocation via the EJBClientInvocationContext#getProxyAttachment()
EJBInvocationHandler.this.putAttachment(AttachmentKeys.VIEW_CLASS_DATA_COMPRESSION_HINT_ATTACHMENT_KEY, compressionHint);
}
@Override
public void scanMethod(Method method) {
final CompressionHint compressionHint = (CompressionHint) method.getAnnotation(CompressionHint.class);
// nothing to do
if (compressionHint == null) {
return;
}
Map annotatedMethods = EJBInvocationHandler.this.getAttachment(AttachmentKeys.VIEW_METHOD_DATA_COMPRESSION_HINT_ATTACHMENT_KEY);
if (annotatedMethods == null) {
// Intentionally avoiding IdentityHashMap since it doesn't work out because the methods being scanned here and the methods being invoked upon later, aren't the "same" (i.e. this method != that method)
annotatedMethods = new HashMap();
EJBInvocationHandler.this.putAttachment(AttachmentKeys.VIEW_METHOD_DATA_COMPRESSION_HINT_ATTACHMENT_KEY, annotatedMethods);
}
annotatedMethods.put(method, compressionHint);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy