/* * JBoss, Home of Professional Open Source. * Copyright 2012, 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.naming.remote.common.ejb; import org.jboss.ejb.client.Affinity; import org.jboss.ejb.client.EJBLocator; import org.jboss.ejb.client.SessionID; import org.jboss.ejb.client.remoting.PackedInteger; import org.jboss.marshalling.AbstractClassResolver; import org.jboss.marshalling.ByteInput; import org.jboss.marshalling.ByteOutput; import org.jboss.marshalling.ClassTable; import org.jboss.marshalling.Marshaller; import org.jboss.marshalling.MarshallerFactory; import org.jboss.marshalling.Marshalling; import org.jboss.marshalling.MarshallingConfiguration; import org.jboss.marshalling.ObjectTable; import org.jboss.marshalling.Unmarshaller; import java.io.DataInput; import java.io.DataOutput; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.OutputStream; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; /** * @author Jaikiran Pai */ public class DummyProtocolHandler { private static final char METHOD_PARAM_TYPE_SEPARATOR = ','; private final MarshallerFactory marshallerFactory; private static final byte HEADER_SESSION_OPEN_RESPONSE = 0x02; private static final byte HEADER_INVOCATION_REQUEST = 0x03; private static final byte HEADER_INVOCATION_CANCEL_REQUEST = 0x04; private static final byte HEADER_INVOCATION_RESPONSE = 0x05; private static final byte HEADER_MODULE_AVAILABLE = 0x08; private static final byte HEADER_MODULE_UNAVAILABLE = 0x09; private static final byte HEADER_NO_SUCH_EJB_FAILURE = 0x0A; private static final byte HEADER_INVOCATION_EXCEPTION = 0x06; private static final byte HEADER_ASYNC_METHOD_NOTIFICATION = 0x0E; private static final ClassTable PROTO_CLASS_TABLE; private static final ObjectTable PROTO_OBJECT_TABLE; private static Field accessible(Field field) { field.setAccessible(true); return field; } static { try { Class<? extends ClassTable> pct = Class.forName("org.jboss.ejb.client.remoting.ProtocolV1ClassTable", false, PackedInteger.class.getClassLoader()).asSubclass(ClassTable.class); Class<? extends ObjectTable> pot = Class.forName("org.jboss.ejb.client.remoting.ProtocolV1ObjectTable", false, PackedInteger.class.getClassLoader()).asSubclass(ObjectTable.class); PROTO_CLASS_TABLE = pct.cast(accessible(pct.getDeclaredField("INSTANCE")).get(null)); PROTO_OBJECT_TABLE = pot.cast(accessible(pot.getDeclaredField("INSTANCE")).get(null)); } catch (ClassNotFoundException e) { throw new NoClassDefFoundError(e.getMessage()); } catch (NoSuchFieldException e) { throw new NoSuchFieldError(e.getMessage()); } catch (IllegalAccessException e) { throw new IllegalAccessError(e.getMessage()); } } public DummyProtocolHandler(final String marshallerType) { this.marshallerFactory = Marshalling.getProvidedMarshallerFactory(marshallerType); if (this.marshallerFactory == null) { throw new RuntimeException("Could not find a marshaller factory for " + marshallerType + " marshalling strategy"); } } public MethodInvocationRequest readMethodInvocationRequest(final DataInput input, final ClassLoader cl) throws IOException { // read the invocation id final short invocationId = input.readShort(); final String methodName = input.readUTF(); // method signature String[] methodParamTypes = null; final String signature = input.readUTF(); if (signature.isEmpty()) { methodParamTypes = new String[0]; } else { methodParamTypes = signature.split(String.valueOf(METHOD_PARAM_TYPE_SEPARATOR)); } // unmarshall the locator and the method params final Object[] methodParams = new Object[methodParamTypes.length]; final Unmarshaller unmarshaller = this.prepareForUnMarshalling(input); String appName = null; String moduleName = null; String distinctName = null; String beanName = null; EJBLocator ejbLocator = null; try { appName = (String) unmarshaller.readObject(); moduleName = (String) unmarshaller.readObject(); distinctName = (String) unmarshaller.readObject(); beanName = (String) unmarshaller.readObject(); ejbLocator = (EJBLocator) unmarshaller.readObject(); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } for (int i = 0; i < methodParamTypes.length; i++) { try { methodParams[i] = unmarshaller.readObject(); } catch (ClassNotFoundException cnfe) { throw new RuntimeException(cnfe); } } // unmarshall the attachments final Map<String, Object> attachments; try { attachments = this.readAttachments(unmarshaller); } catch (ClassNotFoundException cnfe) { throw new RuntimeException(cnfe); } unmarshaller.finish(); return new MethodInvocationRequest(invocationId, appName, moduleName, distinctName, beanName, ejbLocator.getViewType().getName(), methodName, methodParamTypes, methodParams, attachments); } public void writeMethodInvocationResponse(final DataOutput output, final short invocationId, final Object result, final Map<String, Object> attachments) throws IOException { if (output == null) { throw new IllegalArgumentException("Cannot write to null output"); } // write invocation response header output.write(HEADER_INVOCATION_RESPONSE); // write the invocation id output.writeShort(invocationId); // write out the result final Marshaller marshaller = this.prepareForMarshalling(output); marshaller.writeObject(result); // write the attachments this.writeAttachments(marshaller, attachments); marshaller.finish(); } public void writeException(final DataOutput output, final short invocationId, final Throwable t, final Map<String, Object> attachments) throws IOException { // write the header output.write(HEADER_INVOCATION_EXCEPTION); // write the invocation id output.writeShort(invocationId); // write out the exception final Marshaller marshaller = this.prepareForMarshalling(output); marshaller.writeObject(t); // write the attachments this.writeAttachments(marshaller, attachments); // finish marshalling marshaller.finish(); } private void writeInvocationFailure(final DataOutput output, final byte messageHeader, final short invocationId, final String failureMessage) throws IOException { // write header output.writeByte(messageHeader); // write invocation id output.writeShort(invocationId); // write the failure message output.writeUTF(failureMessage); } public void writeNoSuchEJBFailureMessage(final DataOutput output, final short invocationId, final String appName, final String moduleName, final String distinctName, final String beanName, final String viewClassName) throws IOException { final StringBuffer sb = new StringBuffer("No such EJB["); sb.append("appname=").append(appName).append(","); sb.append("modulename=").append(moduleName).append(","); sb.append("distinctname=").append(distinctName).append(","); sb.append("beanname=").append(beanName); if (viewClassName != null) { sb.append(",").append("viewclassname=").append(viewClassName); } sb.append("]"); this.writeInvocationFailure(output, HEADER_NO_SUCH_EJB_FAILURE, invocationId, sb.toString()); } public void writeSessionId(final DataOutput output, final short invocationId, final SessionID sessionID, final Affinity hardAffinity) throws IOException { final byte[] sessionIdBytes = sessionID.getEncodedForm(); // write out header output.writeByte(HEADER_SESSION_OPEN_RESPONSE); // write out invocation id output.writeShort(invocationId); // session id byte length PackedInteger.writePackedInteger(output, sessionIdBytes.length); // write out the session id bytes output.write(sessionIdBytes); // now marshal the hard affinity associated with this session final Marshaller marshaller = this.prepareForMarshalling(output); marshaller.writeObject(hardAffinity); // finish marshalling marshaller.finish(); } public void writeAsyncMethodNotification(final DataOutput output, final short invocationId) throws IOException { // write the header output.write(HEADER_ASYNC_METHOD_NOTIFICATION); // write the invocation id output.writeShort(invocationId); } private Map<String, Object> readAttachments(final ObjectInput input) throws IOException, ClassNotFoundException { final int numAttachments = input.readByte(); if (numAttachments == 0) { return null; } final Map<String, Object> attachments = new HashMap<String, Object>(numAttachments); for (int i = 0; i < numAttachments; i++) { // read the key final String key = (String) input.readObject(); // read the attachment value final Object val = input.readObject(); attachments.put(key, val); } return attachments; } private void writeAttachments(final ObjectOutput output, final Map<String, Object> attachments) throws IOException { if (attachments == null) { output.writeByte(0); return; } // write the attachment count PackedInteger.writePackedInteger(output, attachments.size()); for (Map.Entry<String, Object> entry : attachments.entrySet()) { output.writeObject(entry.getKey()); output.writeObject(entry.getValue()); } } /** * Creates and returns a {@link org.jboss.marshalling.Marshaller} which is ready to be used for marshalling. The {@link org.jboss.marshalling.Marshaller#start(org.jboss.marshalling.ByteOutput)} * will be invoked by this method, to use the passed {@link java.io.DataOutput dataOutput}, before returning the marshaller. * * @param dataOutput The {@link java.io.DataOutput} to which the data will be marshalled * @return * @throws java.io.IOException */ private Marshaller prepareForMarshalling(final DataOutput dataOutput) throws IOException { final Marshaller marshaller = this.getMarshaller(); final OutputStream outputStream = new OutputStream() { @Override public void write(int b) throws IOException { final int byteToWrite = b & 0xff; dataOutput.write(byteToWrite); } }; final ByteOutput byteOutput = Marshalling.createByteOutput(outputStream); // start the marshaller marshaller.start(byteOutput); return marshaller; } /** * Creates and returns a {@link org.jboss.marshalling.Marshaller} * * @return * @throws java.io.IOException */ private Marshaller getMarshaller() throws IOException { final MarshallingConfiguration marshallingConfiguration = new MarshallingConfiguration(); marshallingConfiguration.setClassTable(PROTO_CLASS_TABLE); marshallingConfiguration.setObjectTable(PROTO_OBJECT_TABLE); marshallingConfiguration.setVersion(2); return marshallerFactory.createMarshaller(marshallingConfiguration); } /** * Creates and returns a {@link org.jboss.marshalling.Unmarshaller} which is ready to be used for unmarshalling. The {@link org.jboss.marshalling.Unmarshaller#start(org.jboss.marshalling.ByteInput)} * will be invoked by this method, to use the passed {@link java.io.DataInput dataInput}, before returning the unmarshaller. * * @param dataInput The data input from which to unmarshall * @return * @throws java.io.IOException */ private Unmarshaller prepareForUnMarshalling(final DataInput dataInput) throws IOException { final Unmarshaller unmarshaller = this.getUnMarshaller(); final InputStream is = new InputStream() { @Override public int read() throws IOException { try { final int b = dataInput.readByte(); return b & 0xff; } catch (EOFException eof) { return -1; } } }; final ByteInput byteInput = Marshalling.createByteInput(is); // start the unmarshaller unmarshaller.start(byteInput); return unmarshaller; } /** * Creates and returns a {@link org.jboss.marshalling.Unmarshaller} * * @return * @throws java.io.IOException */ private Unmarshaller getUnMarshaller() throws IOException { final MarshallingConfiguration marshallingConfiguration = new MarshallingConfiguration(); marshallingConfiguration.setVersion(2); marshallingConfiguration.setClassTable(PROTO_CLASS_TABLE); marshallingConfiguration.setObjectTable(PROTO_OBJECT_TABLE); marshallingConfiguration.setClassResolver(TCCLClassResolver.INSTANCE); return marshallerFactory.createUnmarshaller(marshallingConfiguration); } /** * A {@link org.jboss.marshalling.ClassResolver} which returns the context classloader associated * with the thread, when the {@link #getClassLoader()} is invoked */ private static final class TCCLClassResolver extends AbstractClassResolver { static TCCLClassResolver INSTANCE = new TCCLClassResolver(); private TCCLClassResolver() { } @Override protected ClassLoader getClassLoader() { return Thread.currentThread().getContextClassLoader(); } } }