/* * 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.openejb.core.mdb; import org.apache.openejb.ApplicationException; import org.apache.openejb.BeanContext; import org.apache.openejb.SystemException; import org.apache.openejb.resource.activemq.jms2.DelegateMessage; import org.apache.openejb.resource.activemq.jms2.JMS2; import javax.ejb.EJBException; import javax.jms.Message; import javax.resource.spi.ApplicationServerInternalException; import javax.resource.spi.UnavailableException; import javax.resource.spi.endpoint.MessageEndpoint; import javax.transaction.xa.XAResource; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Arrays; public class EndpointHandler implements InvocationHandler, MessageEndpoint { private volatile Boolean isAmq; private static enum State { /** * The handler has been initialized and is ready for invoation */ NONE, /** * The beforeDelivery method has been called, and the next method called must be a message delivery method * or release. */ BEFORE_CALLED, /** * The message delivery method has been called successfully, and the next method called must be * another message delivery method, afterDelivery, or release. */ METHOD_CALLED, /** * The message delivery threw a system exception, and the next method called must be afterDelivery * or release. This state notified the afterDelivery method that the instace must be replaced with a new * instance. */ SYSTEM_EXCEPTION, /** * This message endpoint handler has been released and can no longer be used. */ RELEASED } private final MdbContainer container; private final BeanContext deployment; private final MdbInstanceFactory instanceFactory; private final XAResource xaResource; private State state = State.NONE; private Object instance; public EndpointHandler(final MdbContainer container, final BeanContext deployment, final MdbInstanceFactory instanceFactory, final XAResource xaResource) throws UnavailableException { this.container = container; this.deployment = deployment; this.instanceFactory = instanceFactory; this.xaResource = xaResource; instance = instanceFactory.createInstance(false); } // private static void logTx() { // TransactionManager transactionManager = SystemInstance.get().getComponent(TransactionManager.class); // Transaction transaction = null; // String status = "ERROR"; // try { // transaction = transactionManager.getTransaction(); // int txStatus; // if (transaction != null) { // txStatus = transaction.getStatus(); // } else { // txStatus = Status.STATUS_NO_TRANSACTION; // } // switch (txStatus) { // case Status.STATUS_ACTIVE: // status = "STATUS_ACTIVE"; // break; // case Status.STATUS_MARKED_ROLLBACK: // status = "MARKED_ROLLBACK"; // break; // case Status.STATUS_PREPARED: // status = "PREPARED"; // break; // case Status.STATUS_COMMITTED: // status = "COMMITTED"; // break; // case Status.STATUS_ROLLEDBACK: // status = "ROLLEDBACK"; // break; // case Status.STATUS_UNKNOWN: // status = "UNKNOWN"; // break; // case Status.STATUS_NO_TRANSACTION: // status = "NO_TRANSACTION"; // break; // case Status.STATUS_PREPARING: // status = "PREPARING"; // break; // case Status.STATUS_COMMITTING: // status = "COMMITTING"; // break; // case Status.STATUS_ROLLING_BACK: // status = "ROLLING_BACK"; // break; // default: // status = "UNKNOWN " + txStatus; // } // } catch (javax.transaction.SystemException e) { // } // System.out.println("\n" + // "***************************************\n" + // "transaction " + transaction + "\n" + // " status " + status + "\n" + // "***************************************\n\n"); // // } public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { // System.out.println("\n" + // "***************************************\n" + // "Endpoint invoked " + method + "\n" + // "***************************************\n\n"); final String methodName = method.getName(); final Class<?>[] parameterTypes = method.getParameterTypes(); if (method.getDeclaringClass() == Object.class) { if ("toString".equals(methodName) && parameterTypes.length == 0) { return toString(); } else if ("equals".equals(methodName) && parameterTypes.length == 1) { return equals(args[0]); } else if ("hashCode".equals(methodName) && parameterTypes.length == 0) { return hashCode(); } else { throw new UnsupportedOperationException("Unkown method: " + method); } } // try { if ("beforeDelivery".equals(methodName) && Arrays.deepEquals(new Class[]{Method.class}, parameterTypes)) { beforeDelivery((Method) args[0]); return null; } else if ("afterDelivery".equals(methodName) && parameterTypes.length == 0) { afterDelivery(); return null; } else if ("release".equals(methodName) && parameterTypes.length == 0) { release(); return null; } else { final Object value = deliverMessage(method, args); return value; } // } finally { logTx(); } } public void beforeDelivery(final Method method) throws ApplicationServerInternalException { // verify current state switch (state) { case RELEASED: throw new IllegalStateException("Message endpoint factory has been released"); case BEFORE_CALLED: throw new IllegalStateException("beforeDelivery can not be called again until message is delivered and afterDelivery is called"); case METHOD_CALLED: case SYSTEM_EXCEPTION: throw new IllegalStateException("The last message delivery must be completed with an afterDeliver before beforeDeliver can be called again"); } // call beforeDelivery on the container try { container.beforeDelivery(deployment, instance, method, xaResource); } catch (final SystemException se) { final Throwable throwable = se.getRootCause() != null ? se.getRootCause() : se; throw new ApplicationServerInternalException(throwable); } // before completed successfully we are now ready to invoke bean state = State.BEFORE_CALLED; } public Object deliverMessage(final Method method, final Object[] args) throws Throwable { boolean callBeforeAfter = false; // verify current state switch (state) { case NONE: try { beforeDelivery(method); } catch (final ApplicationServerInternalException e) { throw (EJBException) new EJBException().initCause(e.getCause()); } callBeforeAfter = true; state = State.METHOD_CALLED; break; case BEFORE_CALLED: state = State.METHOD_CALLED; break; case RELEASED: throw new IllegalStateException("Message endpoint factory has been released"); case METHOD_CALLED: case SYSTEM_EXCEPTION: throw new IllegalStateException("The last message delivery must be completed with an afterDeliver before another message can be delivered"); } Throwable throwable = null; Object value = null; try { // deliver the message value = container.invoke(instance, method, null, wrapMessageForAmq5(args)); } catch (final SystemException se) { throwable = se.getRootCause() != null ? se.getRootCause() : se; state = State.SYSTEM_EXCEPTION; } catch (final ApplicationException ae) { throwable = ae.getRootCause() != null ? ae.getRootCause() : ae; } finally { // if the adapter is not using before/after, we must call afterDelivery to clean up if (callBeforeAfter) { try { afterDelivery(); } catch (final ApplicationServerInternalException e) { throwable = throwable == null ? e.getCause() : throwable; } catch (final UnavailableException e) { throwable = throwable == null ? e : throwable; } } } if (throwable != null) { throwable.printStackTrace(); if (isValidException(method, throwable)) { throw throwable; } else { throw new EJBException().initCause(throwable); } } return value; } // workaround for AMQ 5/JMS 2 support private Object[] wrapMessageForAmq5(final Object[] args) { if (args == null || args.length != 1 || DelegateMessage.class.isInstance(args[0])) { return args; } if (isAmq == null) { synchronized (this) { if (isAmq == null) { isAmq = args[0].getClass().getName().startsWith("org.apache.activemq."); } } } if (isAmq) { args[0] = JMS2.wrap(Message.class.cast(args[0])); } return args; } public void afterDelivery() throws ApplicationServerInternalException, UnavailableException { // verify current state switch (state) { case RELEASED: throw new IllegalStateException("Message endpoint factory has been released"); case BEFORE_CALLED: throw new IllegalStateException("Exactally one message must be delivered between beforeDelivery and afterDelivery"); case NONE: throw new IllegalStateException("afterDelivery may only be called if message delivery began with a beforeDelivery call"); } // call afterDelivery on the container boolean exceptionThrown = false; try { container.afterDelivery(instance); } catch (final SystemException se) { exceptionThrown = true; final Throwable throwable = se.getRootCause() != null ? se.getRootCause() : se; throwable.printStackTrace(); throw new ApplicationServerInternalException(throwable); } finally { if (state == State.SYSTEM_EXCEPTION) { recreateInstance(exceptionThrown); } // we are now in the default NONE state state = State.NONE; } } private void recreateInstance(final boolean exceptionAlreadyThrown) throws UnavailableException { try { instance = instanceFactory.recreateInstance(instance); } catch (final UnavailableException e) { // an error occured wile attempting to create the replacement instance // this endpoint is now failed state = State.RELEASED; // if bean threw an exception, do not override that exception if (!exceptionAlreadyThrown) { throw e; } } } public void release() { if (state == State.RELEASED) { return; } state = State.RELEASED; // notify the container try { container.release(deployment, instance); } finally { instanceFactory.freeInstance((Instance) instance, false); instance = null; } } private boolean isValidException(final Method method, final Throwable throwable) { if (throwable instanceof RuntimeException || throwable instanceof Error) { return true; } final Class<?>[] exceptionTypes = method.getExceptionTypes(); for (final Class<?> exceptionType : exceptionTypes) { if (exceptionType.isInstance(throwable)) { return true; } } return false; } }