diff options
author | antelder <antelder@13f79535-47bb-0310-9956-ffa450edef68> | 2009-03-20 11:18:39 +0000 |
---|---|---|
committer | antelder <antelder@13f79535-47bb-0310-9956-ffa450edef68> | 2009-03-20 11:18:39 +0000 |
commit | 2774f360e5ce871037c5699028bb8d2c5ab48f1e (patch) | |
tree | 051078e0ccd6c017e67d4b7de58ade195e0e2ff6 /branches/2.0-M2/modules/binding-ejb-runtime/src/main/java | |
parent | c2228d3d8951c4c52b7cc9508a5675cf9cc439e0 (diff) |
[maven-release-plugin] copy for branch 2.0-M2
git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@756417 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to '')
17 files changed, 3530 insertions, 0 deletions
diff --git a/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/corba/ClassLoadingUtil.java b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/corba/ClassLoadingUtil.java new file mode 100644 index 0000000000..c65868c23b --- /dev/null +++ b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/corba/ClassLoadingUtil.java @@ -0,0 +1,365 @@ +/* + * 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.tuscany.sca.binding.ejb.corba; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * Utility class for loading classes by a variety of name variations. + * <p/> + * Supported names types are: + * <p/> + * 1) Fully qualified class name (e.g., "java.lang.String", "org.apache.geronimo.kernel.ClassLoading" + * 2) Method signature encoding ("Ljava.lang.String;", "J", "I", etc.) + * 3) Primitive type names ("int", "boolean", etc.) + * 4) Method array signature strings ("[I", "[Ljava.lang.String") + * 5) Arrays using Java code format ("int[]", "java.lang.String[][]") + * <p/> + * The classes are loaded using the provided class loader. For the basic types, the primitive + * reflection types are returned. + * + * @version $Rev$ $Date$ + */ +public class ClassLoadingUtil { + + /** + * Table for mapping primitive class names/signatures to the implementing + * class object + */ + private static final HashMap PRIMITIVE_CLASS_MAP = new HashMap(); + + /** + * Table for mapping primitive classes back to their name signature type, which + * allows a reverse mapping to be performed from a class object into a resolvable + * signature. + */ + private static final HashMap CLASS_TO_SIGNATURE_MAP = new HashMap(); + + + /** + * Setup the primitives map. We make any entry for each primitive class using both the + * human readable name and the method signature shorthand type. + */ + static { + PRIMITIVE_CLASS_MAP.put("boolean", boolean.class); + PRIMITIVE_CLASS_MAP.put("Z", boolean.class); + PRIMITIVE_CLASS_MAP.put("byte", byte.class); + PRIMITIVE_CLASS_MAP.put("B", byte.class); + PRIMITIVE_CLASS_MAP.put("char", char.class); + PRIMITIVE_CLASS_MAP.put("C", char.class); + PRIMITIVE_CLASS_MAP.put("short", short.class); + PRIMITIVE_CLASS_MAP.put("S", short.class); + PRIMITIVE_CLASS_MAP.put("int", int.class); + PRIMITIVE_CLASS_MAP.put("I", int.class); + PRIMITIVE_CLASS_MAP.put("long", long.class); + PRIMITIVE_CLASS_MAP.put("J", long.class); + PRIMITIVE_CLASS_MAP.put("float", float.class); + PRIMITIVE_CLASS_MAP.put("F", float.class); + PRIMITIVE_CLASS_MAP.put("double", double.class); + PRIMITIVE_CLASS_MAP.put("D", double.class); + PRIMITIVE_CLASS_MAP.put("void", void.class); + PRIMITIVE_CLASS_MAP.put("V", void.class); + + // Now build a reverse mapping table. The table above has a many-to-one mapping for + // class names. To do the reverse, we need to pick just one. As long as the + // returned name supports "round tripping" of the requests, this will work fine. + + CLASS_TO_SIGNATURE_MAP.put(boolean.class, "Z"); + CLASS_TO_SIGNATURE_MAP.put(byte.class, "B"); + CLASS_TO_SIGNATURE_MAP.put(char.class, "C"); + CLASS_TO_SIGNATURE_MAP.put(short.class, "S"); + CLASS_TO_SIGNATURE_MAP.put(int.class, "I"); + CLASS_TO_SIGNATURE_MAP.put(long.class, "J"); + CLASS_TO_SIGNATURE_MAP.put(float.class, "F"); + CLASS_TO_SIGNATURE_MAP.put(double.class, "D"); + CLASS_TO_SIGNATURE_MAP.put(void.class, "V"); + } + + + /** + * Load a class that matches the requested name, using the provided class loader context. + * <p/> + * The class name may be a standard class name, the name of a primitive type Java + * reflection class (e.g., "boolean" or "int"), or a type in method type signature + * encoding. Array classes in either encoding form are also processed. + * + * @param className The name of the required class. + * @param classLoader The class loader used to resolve the class object. + * @return The Class object resolved from "className". + * @throws ClassNotFoundException When unable to resolve the class object. + * @throws IllegalArgumentException If either argument is null. + */ + public static Class loadClass(String className, ClassLoader classLoader) throws ClassNotFoundException { + + // the tests require IllegalArgumentExceptions for null values on either of these. + if (className == null) { + throw new IllegalArgumentException("className is null"); + } + + if (classLoader == null) { + throw new IllegalArgumentException("classLoader is null"); + } + // The easiest case is a proper class name. We just have the class loader resolve this. + // If the class loader throws a ClassNotFoundException, then we need to check each of the + // special name encodings we support. + try { + return classLoader.loadClass(className); + } catch (ClassNotFoundException ignore) { + // if not found, continue on to the other name forms. + } + + + // The second easiest version to resolve is a direct map to a primitive type name + // or method signature. Check our name-to-class map for one of those. + Class resolvedClass = (Class) PRIMITIVE_CLASS_MAP.get(className); + if (resolvedClass != null) { + return resolvedClass; + } + + // Class names in method signature have the format "Lfully.resolved.name;", + // so if it ends in a semicolon and begins with an "L", this must be in + // this format. Have the class loader try to load this. There are no other + // options if this fails, so just allow the class loader to throw the + // ClassNotFoundException. + if (className.endsWith(";") && className.startsWith("L")) { + // pick out the name portion + String typeName = className.substring(1, className.length() - 1); + // and delegate the loading to the class loader. + return classLoader.loadClass(typeName); + } + + // All we have left now are the array types. Method signature array types + // have a series of leading "[" characters to specify the number of dimensions. + // The other array type we handle uses trailing "[]" for the dimensions, just + // like the Java language syntax. + + // first check for the signature form ([[[[type). + if (className.charAt(0) == '[') { + // we have at least one array marker, now count how many leading '['s we have + // to get the dimension count. + int count = 0; + int nameLen = className.length(); + + while (count < nameLen && className.charAt(count) == '[') { + count++; + } + + // pull of the name subtype, which is everything after the last '[' + String arrayTypeName = className.substring(count, className.length()); + // resolve the type using a recursive call, which will load any of the primitive signature + // types as well as class names. + Class arrayType = loadClass(arrayTypeName, classLoader); + + // Resolving array types require a little more work. The array classes are + // created dynamically when the first instance of a given dimension and type is + // created. We need to create one using reflection to do this. + return getArrayClass(arrayType, count); + } + + + // ok, last chance. Now check for an array specification in Java language + // syntax. This will be a type name followed by pairs of "[]" to indicate + // the number of dimensions. + if (className.endsWith("[]")) { + // get the base component class name and the arrayDimensions + int count = 0; + int position = className.length(); + + while (position > 1 && className.substring(position - 2, position).equals("[]")) { + // count this dimension + count++; + // and step back the probe position. + position -= 2; + } + + // position now points at the location of the last successful test. This makes it + // easy to pick off the class name. + + String typeName = className.substring(0, position); + + // load the base type, again, doing this recursively + Class arrayType = loadClass(typeName, classLoader); + // and turn this into the class object + return getArrayClass(arrayType, count); + } + + throw new ClassNotFoundException("Could not load class " + className + " from unknown classloader; " + classLoader); + } + + + /** + * Map a class object back to a class name. The returned class object + * must be "round trippable", which means + * <p/> + * type == ClassLoading.loadClass(ClassLoading.getClassName(type), classLoader) + * <p/> + * must be true. To ensure this, the class name is always returned in + * method signature format. + * + * @param type The class object we convert into name form. + * @return A string representation of the class name, in method signature + * format. + */ + public static String getClassName(Class type) { + StringBuffer name = new StringBuffer(); + + // we test these in reverse order from the resolution steps, + // first handling arrays, then primitive types, and finally + // "normal" class objects. + + // First handle arrays. If a class is an array, the type is + // element stored at that level. So, for a 2-dimensional array + // of ints, the top-level type will be "[I". We need to loop + // down the hierarchy until we hit a non-array type. + while (type.isArray()) { + // add another array indicator at the front of the name, + // and continue with the next type. + name.append('['); + type = type.getComponentType(); + } + + // we're down to the base type. If this is a primitive, then + // we poke in the single-character type specifier. + if (type.isPrimitive()) { + name.append((String) CLASS_TO_SIGNATURE_MAP.get(type)); + } + // a "normal" class. This gets expressing using the "Lmy.class.name;" syntax. + else { + name.append('L'); + name.append(type.getName()); + name.append(';'); + } + return name.toString(); + } + + private static Class getArrayClass(Class type, int dimension) { + // Array.newInstance() requires an array of the requested number of dimensions + // that gives the size for each dimension. We just request 0 in each of the + // dimensions, which is not unlike a black hole singularity. + int[] dimensions = new int[dimension]; + // create an instance and return the associated class object. + return Array.newInstance(type, dimensions).getClass(); + } + + public static Set getAllTypes(Class type) { + Set allTypes = new LinkedHashSet(); + allTypes.add(type); + allTypes.addAll(getAllSuperClasses(type)); + allTypes.addAll(getAllInterfaces(type)); + return allTypes; + } + + private static Set getAllSuperClasses(Class clazz) { + Set allSuperClasses = new LinkedHashSet(); + for (Class superClass = clazz.getSuperclass(); superClass != null; superClass = superClass.getSuperclass()) { + allSuperClasses.add(superClass); + } + return allSuperClasses; + } + + private static Set getAllInterfaces(Class clazz) { + Set allInterfaces = new LinkedHashSet(); + LinkedList stack = new LinkedList(); + stack.addAll(Arrays.asList(clazz.getInterfaces())); + while (!stack.isEmpty()) { + Class intf = (Class) stack.removeFirst(); + if (!allInterfaces.contains(intf)) { + allInterfaces.add(intf); + stack.addAll(Arrays.asList(intf.getInterfaces())); + } + } + return allInterfaces; + } + + public static Set reduceInterfaces(Set source) { + Class[] classes = (Class[]) source.toArray(new Class[source.size()]); + classes = reduceInterfaces(classes); + return new LinkedHashSet(Arrays.asList(classes)); + } + + /** + * If there are multiple interfaces, and some of them extend each other, + * eliminate the superclass in favor of the subclasses that extend them. + * + * If one of the entries is a class (not an interface), make sure it's + * the first one in the array. If more than one of the entries is a + * class, throws an IllegalArgumentException + * + * @param source the original list of interfaces + * @return the equal or smaller list of interfaces + */ + public static Class[] reduceInterfaces(Class[] source) { + // use a copy of the source array + source = (Class[]) source.clone(); + + for (int leftIndex = 0; leftIndex < source.length-1; leftIndex++) { + Class left = source[leftIndex]; + if(left == null) { + continue; + } + + for (int rightIndex = leftIndex +1; rightIndex < source.length; rightIndex++) { + Class right = source[rightIndex]; + if(right == null) { + continue; + } + + if(left == right || right.isAssignableFrom(left)) { + // right is the same as class or a sub class of left + source[rightIndex] = null; + } else if(left.isAssignableFrom(right)) { + // left is the same as class or a sub class of right + source[leftIndex] = null; + + // the left has been eliminated; move on to the next left + break; + } + } + } + + Class clazz = null; + for (int i = 0; i < source.length; i++) { + if (source[i] != null && !source[i].isInterface()) { + if (clazz != null) { + throw new IllegalArgumentException("Source contains two classes which are not subclasses of each other: " + clazz.getName() + ", " + source[i].getName()); + } + clazz = source[i]; + source[i] = null; + } + } + + List list = new ArrayList(source.length); + if (clazz != null) list.add(clazz); + for (int i = 0; i < source.length; i++) { + if(source[i] != null) { + list.add(source[i]); + } + } + return (Class[]) list.toArray(new Class[list.size()]); + } +} + diff --git a/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/corba/DynamicStubClassLoader.java b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/corba/DynamicStubClassLoader.java new file mode 100644 index 0000000000..82554a1c8f --- /dev/null +++ b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/corba/DynamicStubClassLoader.java @@ -0,0 +1,150 @@ +/* + * 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.tuscany.sca.binding.ejb.corba; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import javax.rmi.CORBA.Stub; + +import net.sf.cglib.core.NamingPolicy; +import net.sf.cglib.core.Predicate; +import net.sf.cglib.proxy.Callback; +import net.sf.cglib.proxy.CallbackFilter; +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.FixedValue; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.NoOp; + +/** + * @version $Revision$ $Date$ + */ +public class DynamicStubClassLoader extends ClassLoader { + private static final String PACKAGE_PREFIX = "org.omg.stub."; + + @Override + public synchronized Class loadClass(final String name) throws ClassNotFoundException { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + + // check if the stub already exists first + try { + return classLoader.loadClass(name); + } catch (ClassNotFoundException e) { + } + + // if this is not a class from the org.omg.stub name space don't attempt to generate + if (!name.startsWith(PACKAGE_PREFIX)) { + throw new ClassNotFoundException("Could not load class: " + name); + } + + // load the interfaces class we are attempting to create a stub for + Class iface = loadStubInterfaceClass(name, classLoader); + + // create the stub builder + try { + Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(Stub.class); + enhancer.setInterfaces(new Class[] {iface}); + enhancer.setCallbackFilter(FILTER); + enhancer.setCallbackTypes(new Class[] {NoOp.class, MethodInterceptor.class, FixedValue.class}); + enhancer.setUseFactory(false); + enhancer.setClassLoader(classLoader); + enhancer.setNamingPolicy(new NamingPolicy() { + public String getClassName(String s, String s1, Object o, Predicate predicate) { + return name; + } + }); + + // generate the class + Class result = enhancer.createClass(); + assert result != null; + + StubMethodInterceptor interceptor = new StubMethodInterceptor(iface); + Ids ids = new Ids(iface); + Enhancer.registerStaticCallbacks(result, new Callback[] {NoOp.INSTANCE, interceptor, ids}); + + return result; + } catch (RuntimeException e) { + throw e; + } catch (Error e) { + throw e; + } + } + + private Class loadStubInterfaceClass(String name, ClassLoader classLoader) throws ClassNotFoundException { + try { + int begin = name.lastIndexOf('.') + 1; + String iPackage = name.substring(13, begin); + String iName = iPackage + name.substring(begin + 1, name.length() - 5); + + return classLoader.loadClass(iName); + } catch (ClassNotFoundException e) { + // don't log exceptions from CosNaming because it attempts to load every + // class bound into the name server + + //FIXME this variable is never read, can we remove the + // whole block of code?? + //boolean shouldLog = true; + StackTraceElement[] stackTrace = e.getStackTrace(); + for (int i = 0; i < stackTrace.length; i++) { + StackTraceElement stackTraceElement = stackTrace[i]; + if (stackTraceElement.getClassName().equals("org.omg.CosNaming.NamingContextExtPOA") && stackTraceElement + .getMethodName().equals("_invoke")) { + //shouldLog = false; + break; + } + } + + throw new ClassNotFoundException("Unable to generate stub", e); + } + } + + private static final CallbackFilter FILTER = new CallbackFilter() { + public int accept(Method method) { + // we don't intercept non-public methods like finalize + if (!Modifier.isPublic(method.getModifiers())) { + return 0; + } + + if (method.getReturnType().equals(String[].class) && method.getParameterTypes().length == 0 + && method.getName().equals("_ids")) { + return 2; + } + + if (Modifier.isAbstract(method.getModifiers())) { + return 1; + } + + return 0; + } + }; + + private static final class Ids implements FixedValue { + private final String[] typeIds; + + public Ids(Class type) { + typeIds = Java2IDLUtil.createCorbaIds(type); + } + + public Object loadObject() throws Exception { + return typeIds; + } + } + +} diff --git a/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/corba/Java2IDLUtil.java b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/corba/Java2IDLUtil.java new file mode 100644 index 0000000000..de8090c45b --- /dev/null +++ b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/corba/Java2IDLUtil.java @@ -0,0 +1,811 @@ +/* + * 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.tuscany.sca.binding.ejb.corba; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Method; +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.rmi.UnexpectedException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.ejb.spi.HandleDelegate; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.rmi.PortableRemoteObject; + +import org.omg.CORBA.ORB; +import org.omg.CORBA.UserException; +import org.omg.CORBA.portable.IDLEntity; +import org.omg.CORBA.portable.ResponseHandler; +import org.omg.CORBA.portable.UnknownException; +import org.omg.CORBA_2_3.portable.InputStream; +import org.omg.CORBA_2_3.portable.OutputStream; +import org.omg.IOP.Codec; +import org.omg.IOP.CodecFactory; +import org.omg.IOP.ENCODING_CDR_ENCAPS; +import org.omg.IOP.Encoding; + +/** + * Various utility functions. + * <p/> + * Note: #getORB() and #getCodec() rely on UtilInitializer to initialize the ORB and codec. + * + * @version $Rev$ $Date$ + * @see UtilInitializer + */ +public final class Java2IDLUtil { + private static ORB orb; + private static Codec codec; + private static HandleDelegate handleDelegate; + + public static ORB getORB() { + assert orb != null; + return orb; + } + + + + public static void setORB(ORB orb) throws UserException { + if (Java2IDLUtil.orb == null) { + Java2IDLUtil.orb = orb; + CodecFactory factory = (CodecFactory) Java2IDLUtil.orb.resolve_initial_references("CodecFactory"); + codec = factory.create_codec(new Encoding(ENCODING_CDR_ENCAPS.value, (byte) 1, (byte) 2)); + } + } + + public static Codec getCodec() { + assert codec != null; + return codec; + } + + public static HandleDelegate getHandleDelegate() throws NamingException { + if (handleDelegate == null) { + InitialContext ic = new InitialContext(); + handleDelegate = (HandleDelegate) ic.lookup("java:comp/HandleDelegate"); + } + return handleDelegate; + } + + private static final Pattern SCOPED_NAME_EXTRACTION_PATTERN = Pattern.compile("(\\\\\\\\)|(\\\\@)|(@)|(\\z)"); + + /** + * See csiv2 Specification 16.2.5 par. 63-64. We extract the username if any and un-escape any + * escaped \ and @ characters. + * + * @param scopedNameBytes + * @return + * @throws UnsupportedEncodingException + */ + public static String extractUserNameFromScopedName(byte[] scopedNameBytes) throws UnsupportedEncodingException { + String scopedUserName = new String(scopedNameBytes, "UTF8"); + return extractUserNameFromScopedName(scopedUserName); + } + + public static String extractUserNameFromScopedName(String scopedUserName) { + Matcher m = SCOPED_NAME_EXTRACTION_PATTERN.matcher(scopedUserName); + StringBuffer buf = new StringBuffer(); + while (m.find()) { + m.appendReplacement(buf, ""); + if (m.group(1) != null) { + buf.append('\\'); + } else if (m.group(2) != null) { + buf.append("@"); + } else if (m.group(3) != null) { + break; + } + } + return buf.toString(); + } + + private static final Pattern SCOPED_NAME_ESCAPE_PATTERN = Pattern.compile("(\\\\)|(@)"); + + public static String buildScopedUserName(String user, String domain) { + StringBuffer buf = new StringBuffer(); + if (user != null) { + escape(user, buf); + } + if (domain != null) { + buf.append('@'); + escape(domain, buf); + } + return buf.toString(); + } + + private static void escape(String s, StringBuffer buf) { + Matcher m = SCOPED_NAME_ESCAPE_PATTERN.matcher(s); + while (m.find()) { + m.appendReplacement(buf, ""); + if (m.group(1) != null) { + buf.append("\\\\"); + } else if (m.group(2) != null) { + buf.append("\\@"); + } + } + m.appendTail(buf); + } + + + public static String byteToString(byte[] data) { + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < data.length; i++) { + buffer.append(HEXCHAR[(data[i] >>> 4) & 0x0F]); + buffer.append(HEXCHAR[(data[i]) & 0x0F]); + } + return buffer.toString(); + + } + + private static final char[] HEXCHAR = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + public static void writeObject(Class type, Object object, OutputStream out) { + if (type == Void.TYPE) { + // do nothing for a void + } else if (type == Boolean.TYPE) { + out.write_boolean(((Boolean) object).booleanValue()); + } else if (type == Byte.TYPE) { + out.write_octet(((Byte) object).byteValue()); + } else if (type == Character.TYPE) { + out.write_wchar(((Character) object).charValue()); + } else if (type == Double.TYPE) { + out.write_double(((Double) object).doubleValue()); + } else if (type == Float.TYPE) { + out.write_float(((Float) object).floatValue()); + } else if (type == Integer.TYPE) { + out.write_long(((Integer) object).intValue()); + } else if (type == Long.TYPE) { + out.write_longlong(((Long) object).longValue()); + } else if (type == Short.TYPE) { + out.write_short(((Short) object).shortValue()); + } else { + // object types must be written in the context of the CORBA application server + // which properly write replaces our objects for CORBA + // ApplicationServer oldApplicationServer = ServerFederation.getApplicationServer(); + try { + // ServerFederation.setApplicationServer(corbaApplicationServer); + + // todo check if + // copy the result to force replacement + // CORBA does not call writeReplace on remote proxies + // + // HOWEVER, if this is an array, then we don't want to do the replacement + // because we can end up with a replacement element that's not compatible with the + // original array type, which results in an ArrayStoreException. Fortunately, + // the Yoko RMI support appears to be able to sort this out for us correctly. + if (object instanceof Serializable && !object.getClass().isArray()) { + try { + object = copyObj(Thread.currentThread().getContextClassLoader(), object); + } catch (Exception e) { + throw new UnknownException(e); + } + } + + if (type == Object.class || type == Serializable.class) { + javax.rmi.CORBA.Util.writeAny(out, object); + } else if (org.omg.CORBA.Object.class.isAssignableFrom(type)) { + out.write_Object((org.omg.CORBA.Object) object); + } else if (Remote.class.isAssignableFrom(type)) { + javax.rmi.CORBA.Util.writeRemoteObject(out, object); + } else if (type.isInterface() && Serializable.class.isAssignableFrom(type)) { + javax.rmi.CORBA.Util.writeAbstractObject(out, object); + } else { + out.write_value((Serializable) object, type); + } + } finally { + // ServerFederation.setApplicationServer(oldApplicationServer); + } + } + } + + private static Object copyObj(ClassLoader classLoader, Object object) throws IOException, ClassNotFoundException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos); + oos.writeObject(object); + oos.flush(); + oos.close(); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStreamExt ois = new ObjectInputStreamExt(bais, classLoader); + return ois.readObject(); + } + + public static Object readObject(Class type, InputStream in) { + if (type == Void.TYPE) { + return null; + } else if (type == Boolean.TYPE) { + return new Boolean(in.read_boolean()); + } else if (type == Byte.TYPE) { + return new Byte(in.read_octet()); + } else if (type == Character.TYPE) { + return new Character(in.read_wchar()); + } else if (type == Double.TYPE) { + return new Double(in.read_double()); + } else if (type == Float.TYPE) { + return new Float(in.read_float()); + } else if (type == Integer.TYPE) { + return new Integer(in.read_long()); + } else if (type == Long.TYPE) { + return new Long(in.read_longlong()); + } else if (type == Short.TYPE) { + return new Short(in.read_short()); + } else if (type == Object.class || type == Serializable.class) { + return javax.rmi.CORBA.Util.readAny(in); + } else if (org.omg.CORBA.Object.class.isAssignableFrom(type)) { + return in.read_Object(type); + } else if (Remote.class.isAssignableFrom(type)) { + return PortableRemoteObject.narrow(in.read_Object(), type); + } else if (type.isInterface() && Serializable.class.isAssignableFrom(type)) { + return in.read_abstract_interface(); + } else { + return in.read_value(type); + } + } + + public static void throwException(Method method, InputStream in) throws Throwable { + // read the exception id + final String id = in.read_string(); + + // get the class name from the id + if (!id.startsWith("IDL:")) { + return; + } + + Class[] exceptionTypes = method.getExceptionTypes(); + for (int i = 0; i < exceptionTypes.length; i++) { + Class exceptionType = exceptionTypes[i]; + + String exceptionId = getExceptionId(exceptionType); + if (id.equals(exceptionId)) { + throw (Throwable) in.read_value(exceptionType); + } + } + throw new UnexpectedException(id); + } + + public static OutputStream writeUserException(Method method, ResponseHandler reply, Exception exception) throws Exception { + if (exception instanceof RuntimeException || exception instanceof RemoteException) { + throw exception; + } + + Class[] exceptionTypes = method.getExceptionTypes(); + for (int i = 0; i < exceptionTypes.length; i++) { + Class exceptionType = exceptionTypes[i]; + if (!exceptionType.isInstance(exception)) { + continue; + } + + OutputStream out = (OutputStream) reply.createExceptionReply(); + String exceptionId = getExceptionId(exceptionType); + out.write_string(exceptionId); + out.write_value(exception); + return out; + } + throw exception; + } + + public static String getExceptionId(Class exceptionType) { + String exceptionName = exceptionType.getName().replace('.', '/'); + if (exceptionName.endsWith("Exception")) { + exceptionName = exceptionName.substring(0, exceptionName.length() - "Exception".length()); + } + exceptionName += "Ex"; + String exceptionId = "IDL:" + exceptionName + ":1.0"; + return exceptionId; + } + + public static String[] createCorbaIds(Class type) { + List ids = new LinkedList(); + for (Iterator iterator = getAllInterfaces(type).iterator(); iterator.hasNext();) { + Class superInterface = (Class) iterator.next(); + if (Remote.class.isAssignableFrom(superInterface) && superInterface != Remote.class) { + ids.add("RMI:" + superInterface.getName() + ":0000000000000000"); + } + } + return (String[]) ids.toArray(new String[ids.size()]); + } + + private static Set getAllInterfaces(Class intfClass) { + Set allInterfaces = new LinkedHashSet(); + + LinkedList stack = new LinkedList(); + stack.addFirst(intfClass); + + while (!stack.isEmpty()) { + Class intf = (Class) stack.removeFirst(); + allInterfaces.add(intf); + stack.addAll(0, Arrays.asList(intf.getInterfaces())); + } + + return allInterfaces; + } + + public static Map mapMethodToOperation(Class intfClass) { + return iiopMap(intfClass, false); + } + + public static Map mapOperationToMethod(Class intfClass) { + return iiopMap(intfClass, true); + } + + private static Map iiopMap(Class intfClass, boolean operationToMethod) { + Method[] methods = getAllMethods(intfClass); + + // find every valid getter + HashMap getterByMethod = new HashMap(methods.length); + HashMap getterByName = new HashMap(methods.length); + for (int i = 0; i < methods.length; i++) { + Method method = methods[i]; + String methodName = method.getName(); + + // no arguments allowed + if (method.getParameterTypes().length != 0) { + continue; + } + + // must start with get or is + String verb; + if (methodName.startsWith("get") && methodName.length() > 3 && method.getReturnType() != void.class) { + verb = "get"; + } else if (methodName.startsWith("is") && methodName.length() > 2 && method.getReturnType() == boolean.class) { + verb = "is"; + } else { + continue; + } + + // must only throw Remote or Runtime Exceptions + boolean exceptionsValid = true; + Class[] exceptionTypes = method.getExceptionTypes(); + for (int j = 0; j < exceptionTypes.length; j++) { + Class exceptionType = exceptionTypes[j]; + if (!RemoteException.class.isAssignableFrom(exceptionType) && + !RuntimeException.class.isAssignableFrom(exceptionType) && + !Error.class.isAssignableFrom(exceptionType)) { + exceptionsValid = false; + break; + } + } + if (!exceptionsValid) { + continue; + } + + String propertyName; + if (methodName.length() > verb.length() + 1 && Character.isUpperCase(methodName.charAt(verb.length() + 1))) { + propertyName = methodName.substring(verb.length()); + } else { + propertyName = Character.toLowerCase(methodName.charAt(verb.length())) + methodName.substring(verb.length() + 1); + } + getterByMethod.put(method, propertyName); + getterByName.put(propertyName, method); + } + + HashMap setterByMethod = new HashMap(methods.length); + for (int i = 0; i < methods.length; i++) { + Method method = methods[i]; + String methodName = method.getName(); + + // must have exactally one arg + if (method.getParameterTypes().length != 1) { + continue; + } + + // must return non void + if (method.getReturnType() != void.class) { + continue; + } + + // must start with set + if (!methodName.startsWith("set") || methodName.length() <= 3) { + continue; + } + + // must only throw Remote or Runtime Exceptions + boolean exceptionsValid = true; + Class[] exceptionTypes = method.getExceptionTypes(); + for (int j = 0; j < exceptionTypes.length; j++) { + Class exceptionType = exceptionTypes[j]; + if (!RemoteException.class.isAssignableFrom(exceptionType) && + !RuntimeException.class.isAssignableFrom(exceptionType) && + !Error.class.isAssignableFrom(exceptionType)) { + exceptionsValid = false; + break; + } + } + if (!exceptionsValid) { + continue; + } + + String propertyName; + if (methodName.length() > 4 && Character.isUpperCase(methodName.charAt(4))) { + propertyName = methodName.substring(3); + } else { + propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4); + } + + // must have a matching getter + Method getter = (Method) getterByName.get(propertyName); + if (getter == null) { + continue; + } + + // setter property must match getter return value + if (!method.getParameterTypes()[0].equals(getter.getReturnType())) { + continue; + } + setterByMethod.put(method, propertyName); + } + + // index the methods by name... used to determine which methods are overloaded + HashMap overloadedMethods = new HashMap(methods.length); + for (int i = 0; i < methods.length; i++) { + Method method = methods[i]; + if (getterByMethod.containsKey(method) || setterByMethod.containsKey(method)) { + continue; + } + String methodName = method.getName(); + List methodList = (List) overloadedMethods.get(methodName); + if (methodList == null) { + methodList = new LinkedList(); + overloadedMethods.put(methodName, methodList); + } + methodList.add(method); + } + + // index the methods by lower case name... used to determine which methods differ only by case + HashMap caseCollisionMethods = new HashMap(methods.length); + for (int i = 0; i < methods.length; i++) { + Method method = methods[i]; + if (getterByMethod.containsKey(method) || setterByMethod.containsKey(method)) { + continue; + } + String lowerCaseMethodName = method.getName().toLowerCase(); + Set methodSet = (Set) caseCollisionMethods.get(lowerCaseMethodName); + if (methodSet == null) { + methodSet = new HashSet(); + caseCollisionMethods.put(lowerCaseMethodName, methodSet); + } + methodSet.add(method.getName()); + } + + String className = getClassName(intfClass); + Map iiopMap = new HashMap(methods.length); + for (int i = 0; i < methods.length; i++) { + Method method = methods[i]; + + String iiopName = (String) getterByMethod.get(method); + if (iiopName != null) { + // if we have a leading underscore prepend with J + if (iiopName.charAt(0) == '_') { + iiopName = "J_get_" + iiopName.substring(1); + } else { + iiopName = "_get_" + iiopName; + } + } else { + iiopName = (String) setterByMethod.get(method); + if (iiopName != null) { + // if we have a leading underscore prepend with J + if (iiopName.charAt(0) == '_') { + iiopName = "J_set_" + iiopName.substring(1); + } else { + iiopName = "_set_" + iiopName; + } + } else { + iiopName = method.getName(); + + // if we have a leading underscore prepend with J + if (iiopName.charAt(0) == '_') { + iiopName = "J" + iiopName; + } + } + } + + // if this name only differs by case add the case index to the end + Set caseCollisions = (Set) caseCollisionMethods.get(method.getName().toLowerCase()); + if (caseCollisions != null && caseCollisions.size() > 1) { + iiopName += upperCaseIndexString(iiopName); + } + + // if this is an overloaded method append the parameter string + List overloads = (List) overloadedMethods.get(method.getName()); + if (overloads != null && overloads.size() > 1) { + iiopName += buildOverloadParameterString(method.getParameterTypes()); + } + + // if we have a leading underscore prepend with J + iiopName = replace(iiopName, '$', "U0024"); + + // if we have matched a keyword prepend with an underscore + if (keywords.contains(iiopName.toLowerCase())) { + iiopName = "_" + iiopName; + } + + // if the name is the same as the class name, append an underscore + if (iiopName.equalsIgnoreCase(className)) { + iiopName += "_"; + } + + if (operationToMethod) { + iiopMap.put(iiopName, method); + } else { + iiopMap.put(method, iiopName); + } + } + + return iiopMap; + } + + private static Method[] getAllMethods(Class intfClass) { + LinkedList methods = new LinkedList(); + for (Iterator iterator = getAllInterfaces(intfClass).iterator(); iterator.hasNext();) { + Class intf = (Class) iterator.next(); + methods.addAll(Arrays.asList(intf.getDeclaredMethods())); + } + + return (Method[]) methods.toArray(new Method[methods.size()]); + } + + /** + * Return the a string containing an underscore '_' index of each uppercase character in the IIOP name. + * + * This is used for distinction of names that only differ by case, since CORBA does not support case sensitive names. + */ + private static String upperCaseIndexString(String iiopName) { + StringBuffer stringBuffer = new StringBuffer(); + for (int i = 0; i < iiopName.length(); i++) { + char c = iiopName.charAt(i); + if (Character.isUpperCase(c)) { + stringBuffer.append('_').append(i); + } + } + return stringBuffer.toString(); + } + + /** + * Replaces any occurances of the specified "oldChar" with the new string. + * + * This is used to replace occurances if '$' in CORBA names since '$' is a special character + */ + private static String replace(String source, char oldChar, String newString) { + StringBuffer stringBuffer = new StringBuffer(source.length()); + for (int i = 0; i < source.length(); i++) { + char c = source.charAt(i); + if (c == oldChar) { + stringBuffer.append(newString); + } else { + stringBuffer.append(c); + } + } + return stringBuffer.toString(); + } + + /** + * Return the a string containing a double underscore '__' list of parameter types encoded using the Java to IDL rules. + * + * This is used for distinction of methods that only differ by parameter lists. + */ + private static String buildOverloadParameterString(Class[] parameterTypes) { + String name = ""; + if (parameterTypes.length ==0) { + name += "__"; + } else { + for (int i = 0; i < parameterTypes.length; i++) { + Class parameterType = parameterTypes[i]; + name += buildOverloadParameterString(parameterType); + } + } + return name.replace('.', '_'); + } + + /** + * Returns a single parameter type encoded using the Java to IDL rules. + */ + private static String buildOverloadParameterString(Class parameterType) { + String name = "_"; + + int arrayDimensions = 0; + while (parameterType.isArray()) { + arrayDimensions++; + parameterType = parameterType.getComponentType(); + } + + // arrays start with org_omg_boxedRMI_ + if (arrayDimensions > 0) { + name += "_org_omg_boxedRMI"; + } + + // IDLEntity types must be prefixed with org_omg_boxedIDL_ + if (IDLEntity.class.isAssignableFrom(parameterType)) { + name += "_org_omg_boxedIDL"; + } + + // add package... some types have special mappings in corba + String packageName = (String) specialTypePackages.get(parameterType.getName()); + if (packageName == null) { + packageName = getPackageName(parameterType.getName()); + } + if (packageName.length() > 0) { + name += "_" + packageName; + } + + // arrays now contain a dimension indicator + if (arrayDimensions > 0) { + name += "_" + "seq" + arrayDimensions; + } + + // add the class name + String className = (String) specialTypeNames.get(parameterType.getName()); + if (className == null) { + className = buildClassName(parameterType); + } + name += "_" + className; + + return name; + } + + /** + * Returns a string containing an encoded class name. + */ + private static String buildClassName(Class type) { + if (type.isArray()) { + throw new IllegalArgumentException("type is an array: " + type); + } + + // get the classname + String typeName = type.getName(); + int endIndex = typeName.lastIndexOf('.'); + if (endIndex < 0) { + return typeName; + } + StringBuffer className = new StringBuffer(typeName.substring(endIndex + 1)); + + // for innerclasses replace the $ separator with two underscores + // we can't just blindly replace all $ characters since class names can contain the $ character + if (type.getDeclaringClass() != null) { + String declaringClassName = getClassName(type.getDeclaringClass()); + assert className.toString().startsWith(declaringClassName + "$"); + className.replace(declaringClassName.length(), declaringClassName.length() + 1, "__"); + } + + // if we have a leading underscore prepend with J + if (className.charAt(0) == '_') { + className.insert(0, "J"); + } + return className.toString(); + } + + private static String getClassName(Class type) { + if (type.isArray()) { + throw new IllegalArgumentException("type is an array: " + type); + } + + // get the classname + String typeName = type.getName(); + int endIndex = typeName.lastIndexOf('.'); + if (endIndex < 0) { + return typeName; + } + return typeName.substring(endIndex + 1); + } + + private static String getPackageName(String interfaceName) { + int endIndex = interfaceName.lastIndexOf('.'); + if (endIndex < 0) { + return ""; + } + return interfaceName.substring(0, endIndex); + } + + private static final Map specialTypeNames; + private static final Map specialTypePackages; + private static final Set keywords; + + static { + specialTypeNames = new HashMap(); + specialTypeNames.put("boolean", "boolean"); + specialTypeNames.put("char", "wchar"); + specialTypeNames.put("byte", "octet"); + specialTypeNames.put("short", "short"); + specialTypeNames.put("int", "long"); + specialTypeNames.put("long", "long_long"); + specialTypeNames.put("float", "float"); + specialTypeNames.put("double", "double"); + specialTypeNames.put("java.lang.Class", "ClassDesc"); + specialTypeNames.put("java.lang.String", "WStringValue"); + specialTypeNames.put("org.omg.CORBA.Object", "Object"); + + specialTypePackages = new HashMap(); + specialTypePackages.put("boolean", ""); + specialTypePackages.put("char", ""); + specialTypePackages.put("byte", ""); + specialTypePackages.put("short", ""); + specialTypePackages.put("int", ""); + specialTypePackages.put("long", ""); + specialTypePackages.put("float", ""); + specialTypePackages.put("double", ""); + specialTypePackages.put("java.lang.Class", "javax.rmi.CORBA"); + specialTypePackages.put("java.lang.String", "CORBA"); + specialTypePackages.put("org.omg.CORBA.Object", ""); + + keywords = new HashSet(); + keywords.add("abstract"); + keywords.add("any"); + keywords.add("attribute"); + keywords.add("boolean"); + keywords.add("case"); + keywords.add("char"); + keywords.add("const"); + keywords.add("context"); + keywords.add("custom"); + keywords.add("default"); + keywords.add("double"); + keywords.add("enum"); + keywords.add("exception"); + keywords.add("factory"); + keywords.add("false"); + keywords.add("fixed"); + keywords.add("float"); + keywords.add("in"); + keywords.add("inout"); + keywords.add("interface"); + keywords.add("long"); + keywords.add("module"); + keywords.add("native"); + keywords.add("object"); + keywords.add("octet"); + keywords.add("oneway"); + keywords.add("out"); + keywords.add("private"); + keywords.add("public"); + keywords.add("raises"); + keywords.add("readonly"); + keywords.add("sequence"); + keywords.add("short"); + keywords.add("string"); + keywords.add("struct"); + keywords.add("supports"); + keywords.add("switch"); + keywords.add("true"); + keywords.add("truncatable"); + keywords.add("typedef"); + keywords.add("union"); + keywords.add("unsigned"); + keywords.add("valuebase"); + keywords.add("valuetype"); + keywords.add("void"); + keywords.add("wchar"); + keywords.add("wstring"); + } + +} diff --git a/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/corba/ObjectInputStreamExt.java b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/corba/ObjectInputStreamExt.java new file mode 100644 index 0000000000..1be4a98a48 --- /dev/null +++ b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/corba/ObjectInputStreamExt.java @@ -0,0 +1,61 @@ +/* + * 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.tuscany.sca.binding.ejb.corba; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectStreamClass; +import java.lang.reflect.Proxy; + +/** + * @version $Rev$ $Date$ + */ +public class ObjectInputStreamExt extends ObjectInputStream { + + private ClassLoader classloader; + + public ObjectInputStreamExt(InputStream in, ClassLoader loader) throws IOException { + super(in); + this.classloader = loader; + } + + @Override + protected Class resolveClass(ObjectStreamClass classDesc) throws IOException, ClassNotFoundException { + return ClassLoadingUtil.loadClass(classDesc.getName(), classloader); + } + + @Override + protected Class resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException { + Class[] cinterfaces = new Class[interfaces.length]; + for (int i = 0; i < interfaces.length; i++) + cinterfaces[i] = classloader.loadClass(interfaces[i]); + + try { + return Proxy.getProxyClass(classloader, cinterfaces); + } catch (IllegalArgumentException e) { + throw new ClassNotFoundException(null, e); + } + } + + ClassLoader getClassloader() { + return classloader; + } + +} diff --git a/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/corba/StubMethodInterceptor.java b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/corba/StubMethodInterceptor.java new file mode 100644 index 0000000000..3ff0ed552a --- /dev/null +++ b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/corba/StubMethodInterceptor.java @@ -0,0 +1,154 @@ +/* + * 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.tuscany.sca.binding.ejb.corba; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Map; + +import javax.ejb.EJBObject; +import javax.rmi.CORBA.Stub; +import javax.rmi.CORBA.Util; + +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; + +import org.omg.CORBA.SystemException; +import org.omg.CORBA.portable.ApplicationException; +import org.omg.CORBA.portable.RemarshalException; +import org.omg.CORBA.portable.ServantObject; +import org.omg.CORBA_2_3.portable.InputStream; +import org.omg.CORBA_2_3.portable.OutputStream; + +/** + * @version $Revision$ $Date$ + */ +public class StubMethodInterceptor implements MethodInterceptor { + private static final Method ISIDENTICAL; + + static { + try { + ISIDENTICAL = EJBObject.class.getMethod("isIdentical", new Class[]{EJBObject.class}); + } catch (NoSuchMethodException e) { + throw new ExceptionInInitializerError(e); + } + } + + private final Class type; + private final Map operations; + + public StubMethodInterceptor(Class type) { + this.type = type; + this.operations = Collections.unmodifiableMap(org.apache.tuscany.sca.binding.ejb.corba.Java2IDLUtil.mapMethodToOperation(type)); + } + + public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { + Stub stub = (Stub) proxy; + + // handle is identical in stub to avoid unnecessary round trip + if (method.equals(ISIDENTICAL)) { + org.omg.CORBA.Object otherObject = (org.omg.CORBA.Object) args[0]; + return new Boolean(stub._is_equivalent(otherObject)); + } + + // get the operation name object + String operationName = (String) operations.get(method); + if (operationName == null) { + throw new IllegalStateException("Unknown method: " + method); + } + + while (true) { + // if this is a stub to a remote object we invoke over the wire + if (!Util.isLocal(stub)) { + + InputStream in = null; + try { + // create the request output stream + OutputStream out = (OutputStream) stub._request(operationName, true); + + // write the arguments + Class[] parameterTypes = method.getParameterTypes(); + for (int i = 0; i < parameterTypes.length; i++) { + Class parameterType = parameterTypes[i]; + org.apache.tuscany.sca.binding.ejb.corba.Java2IDLUtil.writeObject(parameterType, args[i], out); + } + + // send the invocation + in = (InputStream) stub._invoke(out); + + // read the result + Object result = org.apache.tuscany.sca.binding.ejb.corba.Java2IDLUtil.readObject(method.getReturnType(), in); + return result; + } catch (RemarshalException exception) { + continue; + } catch (ApplicationException exception) { + org.apache.tuscany.sca.binding.ejb.corba.Java2IDLUtil.throwException(method, (InputStream) exception.getInputStream()); + } catch (SystemException e) { + throw Util.mapSystemException(e); + } finally { + stub._releaseReply(in); + } + } else { + // get the servant + ServantObject servantObject = stub._servant_preinvoke(operationName, type); + if (servantObject == null) { + continue; + } + + try { + // copy the arguments + Object[] argsCopy = Util.copyObjects(args, stub._orb()); + + // invoke the servant + Object result = null; + try { + result = method.invoke(servantObject.servant, argsCopy); + } catch (InvocationTargetException e) { + if (e.getCause() != null) { + throw e.getCause(); + } + throw e; + } + + // copy the result + result = Util.copyObject(result, stub._orb()); + + return result; + } catch (Throwable throwable) { + // copy the exception + Throwable throwableCopy = (Throwable) Util.copyObject(throwable, stub._orb()); + + // if it is one of my exception rethrow it + Class[] exceptionTypes = method.getExceptionTypes(); + for (int i = 0; i < exceptionTypes.length; i++) { + Class exceptionType = exceptionTypes[i]; + if (exceptionType.isInstance(throwableCopy)) { + throw throwableCopy; + } + } + + throw Util.wrapException(throwableCopy); + } finally { + stub._servant_postinvoke(servantObject); + } + } + } + } +} diff --git a/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/corba/UtilInitializer.java b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/corba/UtilInitializer.java new file mode 100644 index 0000000000..f9aa9ffa22 --- /dev/null +++ b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/corba/UtilInitializer.java @@ -0,0 +1,69 @@ +/* + * 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.tuscany.sca.binding.ejb.corba; + +import org.omg.CORBA.LocalObject; +import org.omg.PortableInterceptor.ORBInitInfo; +import org.omg.PortableInterceptor.ORBInitializer; + +/** + * The sole purpose of this initializer is to register a non-singleton ORB + * with the class <code>Util</code>. + * + * @version $Revision$ $Date$ + * @see Java2IDLUtil + */ +public class UtilInitializer extends LocalObject implements ORBInitializer { + + private static final long serialVersionUID = 4901857563505370955L; + + /** + * Called during ORB initialization. If it is expected that initial + * services registered by an interceptor will be used by other + * interceptors, then those initial services shall be registered at + * this point via calls to + * <code>ORBInitInfo.register_initial_reference</code>. + * + * @param info provides initialization attributes and operations by + * which Interceptors can be registered. + */ + public void pre_init(ORBInitInfo info) { + } + + /** + * Called during ORB initialization. If a service must resolve initial + * references as part of its initialization, it can assume that all + * initial references will be available at this point. + * <p/> + * Calling the <code>post_init</code> operations is not the final + * task of ORB initialization. The final task, following the + * <code>post_init</code> calls, is attaching the lists of registered + * interceptors to the ORB. Therefore, the ORB does not contain the + * interceptors during calls to <code>post_init</code>. If an + * ORB-mediated call is made from within <code>post_init</code>, no + * request interceptors will be invoked on that call. + * Likewise, if an operation is performed which causes an IOR to be + * created, no IOR interceptors will be invoked. + * + * @param info provides initialization attributes and + * operations by which Interceptors can be registered. + */ + public void post_init(ORBInitInfo info) { + } +} diff --git a/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/provider/EJBBindingInvoker.java b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/provider/EJBBindingInvoker.java new file mode 100644 index 0000000000..9247c1d3db --- /dev/null +++ b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/provider/EJBBindingInvoker.java @@ -0,0 +1,92 @@ +/* + * 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.tuscany.sca.binding.ejb.provider; + +import org.apache.tuscany.sca.binding.ejb.EJBBinding; +import org.apache.tuscany.sca.binding.ejb.util.EJBHandler; +import org.apache.tuscany.sca.binding.ejb.util.NamingEndpoint; +import org.apache.tuscany.sca.interfacedef.Operation; +import org.apache.tuscany.sca.invocation.Invoker; +import org.apache.tuscany.sca.invocation.Message; +import org.apache.tuscany.sca.invocation.DataExchangeSemantics; + +/** + * EJBTargetInvoker + * + * @version $Rev$ $Date$ + */ +public class EJBBindingInvoker implements Invoker, DataExchangeSemantics { + + private Operation operation; + private String location; + private Class serviceInterface; + + public EJBBindingInvoker(EJBBinding ejbBinding, Class serviceInterface, Operation operation) { + this.serviceInterface = serviceInterface; + this.location = ejbBinding.getURI(); + this.operation = operation; + } + + public Message invoke(Message msg) { + ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(serviceInterface.getClassLoader()); + Object resp = doInvoke(msg.getBody()); + msg.setBody(resp); + } catch (Throwable e) { + e.printStackTrace(); + msg.setFaultBody(e); + } finally { + Thread.currentThread().setContextClassLoader(tccl); + } + return msg; + } + + /** + * Invoke a EJB operation + * + * @param payload + * @return + */ + public Object doInvoke(final Object payload) { + + // construct NamingendPoint + NamingEndpoint endpoint = getNamingEndpoint(); + + // lookup home and ejb stub + EJBHandler ejbHandler = new EJBHandler(endpoint, serviceInterface); + + String methodName = operation.getName(); + + // invoke business method on ejb + Object response = ejbHandler.invoke(methodName, (Object[])payload); + + return response; + } + + protected NamingEndpoint getNamingEndpoint() { + return new NamingEndpoint(location); + } + + public boolean allowsPassByReference() { + // EJB RMI/IIOP always pass by value + return true; + } + +} diff --git a/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/provider/EJBBindingProviderFactory.java b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/provider/EJBBindingProviderFactory.java new file mode 100644 index 0000000000..a6ea583d0b --- /dev/null +++ b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/provider/EJBBindingProviderFactory.java @@ -0,0 +1,56 @@ +/* + * 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.tuscany.sca.binding.ejb.provider; + +import org.apache.tuscany.sca.binding.ejb.EJBBinding; +import org.apache.tuscany.sca.core.ExtensionPointRegistry; +import org.apache.tuscany.sca.provider.BindingProviderFactory; +import org.apache.tuscany.sca.provider.ReferenceBindingProvider; +import org.apache.tuscany.sca.provider.ServiceBindingProvider; +import org.apache.tuscany.sca.runtime.RuntimeComponent; +import org.apache.tuscany.sca.runtime.RuntimeComponentReference; +import org.apache.tuscany.sca.runtime.RuntimeComponentService; + +/** + * A factory from creating the EJB binding provider. + * + * @version $Rev$ $Date$ + */ +public class EJBBindingProviderFactory implements BindingProviderFactory<EJBBinding> { + + public EJBBindingProviderFactory(ExtensionPointRegistry extensionPoints) { + } + + public ReferenceBindingProvider createReferenceBindingProvider(RuntimeComponent component, + RuntimeComponentReference reference, + EJBBinding binding) { + return new EJBBindingReferenceBindingProvider(component, reference, binding); + } + + public ServiceBindingProvider createServiceBindingProvider(RuntimeComponent component, + RuntimeComponentService service, + EJBBinding binding) { + return null; + //throw new EJBBindingException("Service Binding not supported for EJB Binding"); + } + + public Class<EJBBinding> getModelType() { + return EJBBinding.class; + } +} diff --git a/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/provider/EJBBindingReferenceBindingProvider.java b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/provider/EJBBindingReferenceBindingProvider.java new file mode 100644 index 0000000000..2a02bf0d08 --- /dev/null +++ b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/provider/EJBBindingReferenceBindingProvider.java @@ -0,0 +1,86 @@ +/* + * 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.tuscany.sca.binding.ejb.provider; + +import org.apache.tuscany.sca.binding.ejb.EJBBinding; +import org.apache.tuscany.sca.interfacedef.InterfaceContract; +import org.apache.tuscany.sca.interfacedef.Operation; +import org.apache.tuscany.sca.interfacedef.java.JavaInterface; +import org.apache.tuscany.sca.invocation.Invoker; +import org.apache.tuscany.sca.provider.ReferenceBindingProvider; +import org.apache.tuscany.sca.runtime.RuntimeComponent; +import org.apache.tuscany.sca.runtime.RuntimeComponentReference; + +/** + * Implementation of the ReferenceBindingProvider for the EJBBinding. + * + * @version $Rev$ $Date$ + */ +public class EJBBindingReferenceBindingProvider implements ReferenceBindingProvider { + private EJBBinding ejbBinding; + private RuntimeComponentReference reference; + + /** + * Constructor + * + * @param component + * @param reference + * @param binding + */ + public EJBBindingReferenceBindingProvider(RuntimeComponent component, + RuntimeComponentReference reference, + EJBBinding binding) { + this.reference = reference; + this.ejbBinding = binding; + } + + /** + * {@inheritDoc} + */ + public Invoker createInvoker(Operation operation) { + return new EJBBindingInvoker(ejbBinding, ((JavaInterface)reference.getInterfaceContract().getInterface()) + .getJavaClass(), operation); + } + + /** + * {@inheritDoc} + */ + public InterfaceContract getBindingInterfaceContract() { + return reference.getInterfaceContract(); + } + + /** + * {@inheritDoc} + */ + public void start() { + } + + /** + * {@inheritDoc} + */ + public void stop() { + } + + /** + * {@inheritDoc} + */ + public boolean supportsOneWayInvocation() { + return true; + } +} diff --git a/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/EJBHandler.java b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/EJBHandler.java new file mode 100644 index 0000000000..c7c0f90710 --- /dev/null +++ b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/EJBHandler.java @@ -0,0 +1,338 @@ +/* + * 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.tuscany.sca.binding.ejb.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.rmi.RemoteException; +import java.util.HashMap; +import java.util.Map; + +import javax.ejb.EJBObject; +import javax.rmi.CORBA.Util; + +import org.apache.tuscany.sca.binding.ejb.corba.ClassLoadingUtil; +import org.omg.CORBA.ORB; +import org.omg.CORBA.SystemException; +import org.omg.CORBA.portable.ApplicationException; +import org.omg.CORBA.portable.ObjectImpl; +import org.omg.CORBA.portable.RemarshalException; +import org.omg.CORBA.portable.ServantObject; +import org.omg.CORBA_2_3.portable.InputStream; +import org.omg.CORBA_2_3.portable.OutputStream; +import org.oasisopen.sca.ServiceRuntimeException; + +/** + * EJBMessageHandler + * + * @version $Rev$ $Date$ + */ +public class EJBHandler { + private static final Map<String, Class> PRIMITIVE_TYPES = new HashMap<String, Class>(); + static { + PRIMITIVE_TYPES.put("boolean", boolean.class); + PRIMITIVE_TYPES.put("byte", byte.class); + PRIMITIVE_TYPES.put("char", char.class); + PRIMITIVE_TYPES.put("short", short.class); + PRIMITIVE_TYPES.put("int", int.class); + PRIMITIVE_TYPES.put("long", long.class); + PRIMITIVE_TYPES.put("float", float.class); + PRIMITIVE_TYPES.put("double", double.class); + PRIMITIVE_TYPES.put("void", void.class); + } + + private Object ejbStub; + + private InterfaceInfo interfaceInfo; + private Class ejbInterface; + + public EJBHandler(NamingEndpoint namingEndpoint, Class ejbInterface) { + this(namingEndpoint, InterfaceInfo.getInstance(ejbInterface)); + this.ejbInterface = ejbInterface; + } + + // locates the stub + private EJBHandler(NamingEndpoint namingEndpoint, InterfaceInfo ejbInterface) { + try { + this.ejbStub = EJBStubHelper.lookup(namingEndpoint, ejbInterface); + this.interfaceInfo = ejbInterface; + } catch (Exception e) { + throw new ServiceRuntimeException(e); + } + } + + private static Class loadClass(final String name) { + try { + return ClassLoadingUtil.loadClass(name, Thread.currentThread().getContextClassLoader()); + } catch (ClassNotFoundException e) { + throw new ServiceRuntimeException(e); + } + } + + // invokes EJB method + public Object invoke(String methodName, Object[] args) { + Object response = null; + try { + if (ejbStub instanceof ObjectImpl) { + ObjectImpl objectImpl = (ObjectImpl)ejbStub; + // TODO: If the Java 2 security is turned on, then + // the ORB will try to create proxy + // from the interfaces defined on the stub + if (System.getSecurityManager() == null && objectImpl._is_local()) { + /* + * CORBA.Stub is what the object from JNDI will be for a + * remote EJB in the same JVM as the client, but with no + * stub classes available on the client + */ + response = invokeLocalCORBACall(objectImpl, methodName, args); + } else { + /* + * _EJBObject_Stub is what the object from JNDI will be for + * a remote EJB with no stub classes available on the client + */ + response = invokeRemoteCORBACall(objectImpl, methodName, args); + } + } else { + /* + * A generated ejb stub or it must be an EJB in the same ear as + * the client or an AppServer with a single ClassLoader, so + * reflection can be used directly on the JNDI + */ + JavaReflectionAdapter reflectionAdapter = + JavaReflectionAdapter.createJavaReflectionAdapter(ejbStub.getClass()); + try { + Method method = reflectionAdapter.getMethod(methodName); + response = method.invoke(ejbStub, args); + } catch (InvocationTargetException e) { + Throwable t = e.getTargetException(); + // FIXME need to throw really a business exception. + // ServiceBusinessException? + // Tuscany core doesn't have ServiceBusinessException + throw new ServiceRuntimeException(t); + } + } + + return response; + } catch (Exception e) { + // FIXME this be business exception? Tuscany core doesn't have + // ServiceBusinessException + throw new ServiceRuntimeException(e); + + } catch (Throwable e) { + throw new ServiceRuntimeException(e); + } + } + + /** + * Get the IDL operation name for a java method + * + * @param methodName java method name + * @return The IDL operation name + */ + private String getOperation(String methodName) { + if (interfaceInfo == null) { + return methodName; + } + MethodInfo methodInfo = interfaceInfo.getMethod(methodName); + if (methodInfo != null) { + return methodInfo.getIDLName(); + } else { + return null; + } + } + + /* + * Derive the EJB interface name from the Stub When loading a stub class + * corresponding to an interface or class <packagename>.<typename>, the + * class <packagename>._<typename>_Stub shall be used if it exists; + * otherwise, the class org.omg.stub.<packagename>._<typename>_Stub shall + * be used. + */ + private static String getInterface(String stubName) { + int index = stubName.lastIndexOf('.'); + String packageName = null; + String typeName = stubName; + if (index != -1) { + packageName = stubName.substring(0, index); + if (packageName.startsWith("org.omg.stub.")) { + packageName = packageName.substring("org.omg.stub.".length()); + } + typeName = stubName.substring(index + 1); + } + if (typeName.startsWith("_") && typeName.endsWith("_Stub")) { + typeName = typeName.substring(1, typeName.length() - "_Stub".length()); + } + if (packageName != null) + return packageName + "." + typeName; + else + return typeName; + } + + /** + * Invoke a method on the local CORBA object + * + * @param stub + * @param methodName + * @param args + * @return + * @throws RemoteException + * @throws ServiceBusinessException + */ + private Object invokeLocalCORBACall(final ObjectImpl stub, String methodName, Object[] args) + throws RemoteException { + + final String operation = getOperation(methodName); + + Class type = loadClass(getInterface(stub.getClass().getName())); + if (type == null) + type = (ejbInterface != null) ? ejbInterface : EJBObject.class; + + ServantObject so = stub._servant_preinvoke(operation, type); + if (so == null) { + // The Servant is not local any more + return invokeRemoteCORBACall(stub, methodName, args); + } + Object[] newArgs = null; + ORB orb = stub._orb(); + try { + if (args != null) + newArgs = Util.copyObjects(args, orb); + JavaReflectionAdapter reflectionAdapter = + JavaReflectionAdapter.createJavaReflectionAdapter(so.servant.getClass()); + Method method = reflectionAdapter.getMethod(methodName); + Object obj = reflectionAdapter.invoke(method, so.servant, newArgs); + Object result = Util.copyObject(obj, orb); + return result; + + } catch (InvocationTargetException e) { + Throwable exCopy = (Throwable)Util.copyObject(e.getTargetException(), orb); + MethodInfo methodInfo = interfaceInfo.getMethod(methodName); + String[] exceptionTypes = methodInfo.getExceptionTypes(); + for (int i = 0; i < exceptionTypes.length; i++) { + Class exceptionType = + methodInfo.getMethod() != null ? methodInfo.getMethod().getExceptionTypes()[i] + : loadClass(exceptionTypes[i]); + if (exceptionType.isAssignableFrom(exCopy.getClass())) + throw new ServiceRuntimeException(exCopy); // FIXME should + // be business + // exception? + } + throw Util.wrapException(exCopy); + } catch (Throwable e) { + // Other exceptions thrown from "invoke" + throw new ServiceRuntimeException(e); + } finally { + stub._servant_postinvoke(so); + } + } + + /** + * Invoke a method on a remote CORBA object + * + * @param stub The remote stub + * @param methodName The name of the method + * @param args Argument list + * @return + * @throws RemoteException + * @throws ServiceBusinessException + */ + private Object invokeRemoteCORBACall(ObjectImpl stub, String methodName, Object[] args) throws RemoteException { + + try { + String operation = getOperation(methodName); + + MethodInfo methodInfo = interfaceInfo.getMethod(methodName); + if (methodInfo == null) { + throw new ServiceRuntimeException("Invalid Method " + methodName); + } + Class[] parameterTypes = null; + Class returnType = null; + if (methodInfo.getMethod() != null) { + parameterTypes = methodInfo.getMethod().getParameterTypes(); + returnType = methodInfo.getMethod().getReturnType(); + } else { + String[] types = methodInfo.getParameterTypes(); + if (args != null) { + if (types.length != args.length) + throw new ServiceRuntimeException( + "The argument list doesn't match the method signature of " + methodName); + } + + parameterTypes = new Class[types.length]; + for (int i = 0; i < types.length; i++) { + parameterTypes[i] = loadClass(types[i]); + } + returnType = loadClass(methodInfo.getReturnType()); + } + + InputStream in = null; + try { + OutputStream out = (OutputStream)stub._request(operation, true); + + for (int i = 0; i < parameterTypes.length; i++) { + // Object arg = (args.length < i) ? null : args[i]; + writeValue(out, args[i], parameterTypes[i]); + } + if (returnType == void.class) { + // void return + stub._invoke(out); + return null; + } else { + // read the return value + in = (InputStream)stub._invoke(out); + Object response = readValue(in, returnType); + return response; + } + + } catch (ApplicationException ex) { + in = (InputStream)ex.getInputStream(); + try { + org.apache.tuscany.sca.binding.ejb.corba.Java2IDLUtil.throwException(methodInfo.getMethod(), in); + return null; + } catch (Throwable e) { + throw new RemoteException(e.getMessage(), e); + } + } catch (RemarshalException ex) { + return invokeRemoteCORBACall(stub, methodName, args); + } finally { + stub._releaseReply(in); + } + } catch (SystemException ex) { + throw Util.mapSystemException(ex); + } + } + + /** + * @param out + * @param value + * @param type + */ + private void writeValue(OutputStream out, Object value, Class type) { + org.apache.tuscany.sca.binding.ejb.corba.Java2IDLUtil.writeObject(type, value, out); + } + + /** + * @param in + * @param type + * @return + */ + private Object readValue(InputStream in, Class type) { + return org.apache.tuscany.sca.binding.ejb.corba.Java2IDLUtil.readObject(type, in); + } +} diff --git a/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/EJBLocator.java b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/EJBLocator.java new file mode 100644 index 0000000000..0b97f42746 --- /dev/null +++ b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/EJBLocator.java @@ -0,0 +1,483 @@ +/* + * 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.tuscany.sca.binding.ejb.util; + +import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; + +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; + +import org.omg.CORBA.ORB; +import org.omg.CosNaming.NamingContextExt; + +/** + * CosNaming utility + * + * @version $Rev$ $Date$ + */ +class EJBLocator { + + /* + * Root Context Initial Reference Key ------------ + * ----------------------------------- Server Root NameServiceServerRoot + * Cell Persistent Root NameServiceCellPersistentRoot Cell Root + * NameServiceCellRoot, NameService Node Root NameServiceNodeRoot + */ + public static final String SERVER_ROOT = "NameServiceServerRoot"; + public static final String CELL_PERSISTENT_ROOT = "NameServiceCellPersistentRoot"; + public static final String CELL_ROOT = "NameServiceCellRoot"; + public static final String NODE_ROOT = "NameServiceNodeRoot"; + public static final String DEFAULT_ROOT = "NameService"; // Same as + // CELL_ROOT + + public static final String DEFAULT_HOST = "127.0.0.1"; // Default host name + // or IP address for + // WebSphere + public static final int DEFAULT_NAMING_PORT = 2809; // Default port + public static final String NAMING_SERVICE = "NameService"; // The name of + // the naming + // service + private static final Set<String> ROOTS = + new HashSet<String>(Arrays.asList(new String[] {SERVER_ROOT, CELL_PERSISTENT_ROOT, CELL_ROOT, DEFAULT_ROOT, + NODE_ROOT})); + + // private static final String CHARS_TO_ESCAPE = "\\/."; + private static final String RFC2396 = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789;/:?@&=+$,-_.!~*'()"; + private static final String HEX = "0123456789ABCDEF"; + + private String hostName = DEFAULT_HOST; + private int port = DEFAULT_NAMING_PORT; + private String root = SERVER_ROOT; + + private ORB orb = null; + private ObjectLocator locator = null; + private boolean managed = true; + + EJBLocator(boolean managed) { + this.managed = managed; + if (!managed) { + String url = AccessController.doPrivileged(new PrivilegedAction<String>() { + public String run() { + return System.getProperty(Context.PROVIDER_URL); + } + }); + processCorbaURL(url); + } + } + + EJBLocator(String hostName, int port) { + this.hostName = (hostName == null) ? DEFAULT_HOST : hostName; + this.port = port > 0 ? port : DEFAULT_NAMING_PORT; + this.root = SERVER_ROOT; + } + + EJBLocator(String hostName, int port, String root) { + this(hostName, port); + if (ROOTS.contains(root)) { + this.root = root; + } else { + throw new IllegalArgumentException(root + " is not a legal root"); + } + } + + EJBLocator(String corbaName, boolean managed) { + this.managed = managed; + if (corbaName.startsWith("corbaname:iiop:")) { + processCorbaURL(corbaName); + } else { + throw new IllegalArgumentException(corbaName + " is not a legal corbaname"); + } + } + + private void processCorbaURL(String url) { + if (url != null && (url.startsWith("corbaname:iiop:") || url.startsWith("corbaloc:iiop:"))) { + /** + * corbaname:iiop:<hostName>:<port>/<root>#name corbaloc:iiop:<hostname>:<port>/<root> + * For example, + * "corbaname:iiop:localhost:2809/NameServiceServerRoot#ejb/MyEJBHome"; + * or "corbaloc:iiop:myhost:2809/NameServiceServerRoot" + */ + String[] parts = url.split("(:|/|#)"); + if (parts.length > 2 && parts[2].length() > 0) { + hostName = parts[2]; // The host name + int index = hostName.lastIndexOf('@'); // version@hostname + if (index != -1) { + hostName = hostName.substring(index + 1); + } + } + if (parts.length > 3 && parts[3].length() > 0) { + port = Integer.parseInt(parts[3]); // The port number + } + if (parts.length > 4 && parts[4].length() > 0) { + root = parts[4]; // The root of naming + } + } + } + + /** + * The corbaloc and corbaname formats enable you to provide a URL to access + * CORBA objects. Use the corbaloc format for resolving to a particular + * CORBAservice without going through a naming service. Use the corbaname + * format to resolve a stringified name from a specific naming context. + */ + + /** + * corbaname Syntax The full corbaname BNF is: <corbaname> = + * "corbaname:"<corbaloc_obj>["#"<string_name>] + * <corbaloc_obj> = <obj_addr_list> ["/"<key_string>] + * <obj_addr_list> = as defined in a corbaloc URL <key_string> = + * as defined in a corbaloc URL <string_name>= stringified Name + * empty_string Where: + * <ul> + * <li>corbaloc_obj: portion of a corbaname URL that identifies the naming + * context. The syntax is identical to its use in a corbaloc URL. + * <li>obj_addr_list: as defined in a corbaloc URL + * <li>key_string: as defined in a corbaloc URL. + * <li>string_name: a stringified Name with URL escapes as defined below. + * </ul> + * + * @param hostName The host name or IP address of the naming server + * @param port The port number of the naming service + * @param root The root of the namespace + * @param name The JNDI name + */ + private static String getCorbaname(String hostName, int port, String root, String name) { + if (name == null) { + return "corbaname:iiop:" + hostName + ":" + port + "/" + root; + } else { + return "corbaname:iiop:" + hostName + ":" + port + "/" + root + "#" + toCorbaname(name); + } + } + + String getCorbaname(String name) { + return getCorbaname(hostName, port, root, name); + } + + /** + * Connect to the ORB. + */ + + // FIXME. May need to change the IBM classes if this binding is contributed + // to Tuscany + public ORB connect() { + if (orb == null) { + Properties props = new Properties(); + /* + * This code is for IBM JVM props.put("org.omg.CORBA.ORBClass", + * "com.ibm.CORBA.iiop.ORB"); + * props.put("com.ibm.CORBA.ORBInitRef.NameService", + * getCorbaloc(NAMING_SERVICE)); + * props.put("com.ibm.CORBA.ORBInitRef.NameServiceServerRoot", + * getCorbaloc("NameServiceServerRoot")); + */ + orb = ORB.init((String[])null, props); + } + return orb; + } + + /** + * Replace substrings + * + * @param source The source string. + * @param match The string to search for within the source string. + * @param replace The replacement for any matching components. + * @return + */ + private static String replace(String source, String match, String replace) { + int index = source.indexOf(match, 0); + if (index >= 0) { + + // We have at least one match, so got to do the + // work... + + StringBuffer result = new StringBuffer(source.length() + 16); + int matchLength = match.length(); + int startIndex = 0; + + while (index >= 0) { + result.append(source.substring(startIndex, index)); + result.append(replace); + startIndex = index + matchLength; + index = source.indexOf(match, startIndex); + } + + // Grab the last piece, if any... + if (startIndex < source.length()) { + result.append(source.substring(startIndex)); + } + + return result.toString(); + + } else { + // No matches, just return the source... + return source; + } + } + + /** + * Resolved the JNDI name from the initial CosNaming context + * + * @param jndiName + * @return resolved CORBA object + * @throws NamingException + */ + private static org.omg.CORBA.Object resovleString(NamingContextExt initCtx, String jndiName) throws NamingException { + try { + String name = stringify(jndiName); + return initCtx.resolve_str(name); + } catch (Exception e) { + NamingException ne = new NamingException(e.getMessage()); + ne.setRootCause(e); + throw ne; + } + } + + /** + * Look up a CORBA object by its JNDI name + * + * @param jndiName + * @return + * @throws NamingException + */ + org.omg.CORBA.Object stringToObject(String jndiName) throws NamingException { + /* + * Using an existing ORB and invoking string_to_object with a CORBA + * object URL with multiple name server addresses to get an initial + * context CORBA object URLs can contain more than one bootstrap server + * address. Use this feature when attempting to obtain an initial + * context from a server cluster. You can specify the bootstrap server + * addresses for all servers in the cluster in the URL. The operation + * will succeed if at least one of the servers is running, eliminating a + * single point of failure. There is no guarantee of any particular + * order in which the address list will be processed. For example, the + * second bootstrap server address may be used to obtain the initial + * context even though the first bootstrap server in the list is + * available. An example of a corbaloc URL with multiple addresses + * follows. obj = + * orb.string_to_object("corbaloc::myhost1:9810,:myhost1:9811,:myhost2:9810/NameService"); + */ + String corbaName = null; + if (jndiName.startsWith("corbaloc:") || jndiName.startsWith("corbaname:")) { + // Keep the qualified URL + corbaName = jndiName; + } else { + // Create a corbaname URL + corbaName = getCorbaname(jndiName); + } + + connect(); + org.omg.CORBA.Object obj = orb.string_to_object(corbaName); + return obj; + } + + private boolean isJndiConfigured() { + if (managed) + return true; + Boolean provided = AccessController.doPrivileged(new PrivilegedAction<Boolean>() { + public Boolean run() { + String initCtxFactory = System.getProperty(Context.INITIAL_CONTEXT_FACTORY); + if (initCtxFactory == null) { + URL file = Thread.currentThread().getContextClassLoader().getResource("jndi.properties"); + if (file != null) { + return Boolean.TRUE; + } else { + return Boolean.FALSE; + } + } else { + return Boolean.TRUE; + } + } + }); + return provided.booleanValue(); + } + + /** + * The character escape rules for the stringified name portion of an + * corbaname are: US-ASCII alphanumeric characters are not escaped. + * Characters outside this range are escaped, except for the following: ; / : ? @ & = + $ , - _ . ! ~ * ' ( ) + * corbaname Escape Mechanism The percent '%' character is used as an + * escape. If a character that requires escaping is present in a name + * component it is encoded as two hexadecimal digits following a "%" + * character to represent the octet. (The first hexadecimal character + * represent the highorder nibble of the octet, the second hexadecimal + * character represents the low-order nibble.) If a '%' is not followed by + * two hex digits, the stringified name is syntactically invalid. + * @param s + * @return RFC2396-encoded stringified name + */ + static String encode2396(String s) { + if (s == null) { + return null; + } + StringBuffer encoded = new StringBuffer(s); + for (int i = 0; i < encoded.length(); i++) { + char c = encoded.charAt(i); + if (RFC2396.indexOf(c) == -1) { + encoded.setCharAt(i, '%'); + char[] ac = Integer.toHexString(c).toCharArray(); + if (ac.length == 2) { + encoded.insert(i + 1, ac); + } else if (ac.length == 1) { + encoded.insert(i + 1, '0'); + encoded.insert(i + 2, ac[0]); + } else { + throw new IllegalArgumentException("Invalid character '" + c + "' in \"" + s + "\""); + } + i += 2; // NOPMD + } + } + return encoded.toString(); + } + + /** + * Decode an RFC2396-encoded string + * + * @param s + * @return Plain string + */ + static String decode2396(String s) { + if (s == null) { + return null; + } + StringBuffer decoded = new StringBuffer(s); + for (int i = 0; i < decoded.length(); i++) { + char c = decoded.charAt(i); + if (c == '%') { + if (i + 2 >= decoded.length()) { + throw new IllegalArgumentException("Incomplete key_string escape sequence"); + } + int j; + j = HEX.indexOf(decoded.charAt(i + 1)) * 16 + HEX.indexOf(decoded.charAt(i + 2)); + decoded.setCharAt(i, (char)j); + decoded.delete(i + 1, i + 3); + } else if (RFC2396.indexOf(c) == -1) { + throw new IllegalArgumentException("Invalid key_string character '" + c + "'"); + } + } + return decoded.toString(); + } + + /** + * The backslash '\' character escapes the reserved meaning of '/', '.', and + * '\' in a stringified name. + * + * @param jndiName + * @return Escaped stringified name for CosNaming + */ + private static String stringify(String jndiName) { + // Escape . into \. since it's an INS naming delimiter + return replace(encode2396(jndiName), ".", "\\."); + } + + /** + * Escape the "." into "%5C%2E" + * + * @param jndiName + * @return corbaname treating "." as a literal + */ + private static String toCorbaname(String jndiName) { + // Escape . into %5C%2E (\.) since it's an INS naming delimiter + // For example, sca.sample.StockQuote ---> + // sca%5C%2Esample%5C%2EStockQuote/StockQuote + return replace(encode2396(jndiName), ".", "%5C%2E"); + } + + private ObjectLocator getObjectLocator() throws NamingException { + if (locator != null) { + return locator; + } + /* + * For managed env, JNDI is assumed to be configured by default For + * unmanaged environment, JNDI could have configured through + * jndi.properties file + */ + if (isJndiConfigured()) { + locator = new JndiLocator(); + } else { // this is definitely JSE env without JNDI configured. Use + // CORBA. + locator = new CosNamingLocator(); + } + return locator; + } + + public Object locate(String jndiName) throws NamingException { + + Object result = getObjectLocator().locate(jndiName); + return result; + } + + private static interface ObjectLocator { + Object locate(String name) throws NamingException; + } + + private final class JndiLocator implements ObjectLocator { + private Context context; + + private JndiLocator() throws NamingException { + /* + final Properties props = AccessController.doPrivileged(new PrivilegedAction<Properties>() { + public Properties run() { + return System.getProperties(); + } + }); + Properties properties = new Properties(); + for (Map.Entry e : props.entrySet()) { + String name = (String)e.getKey(); + if (name.startsWith("java.naming.")) { + properties.setProperty(name, (String)e.getValue()); + } + } + // System.out.println(properties); + this.context = new InitialContext(properties); + */ + this.context = new InitialContext(); + } + + public Object locate(String name) throws NamingException { + return context.lookup(name); + } + } + + private final class CosNamingLocator implements ObjectLocator { + private NamingContextExt context; + + private CosNamingLocator() { + } + + public Object locate(String name) throws NamingException { + if (context != null) { + return resovleString(context, name); + } else { + return stringToObject(name); + } + } + } + + public void setHostEnv(boolean managed) { + this.managed = managed; + } +} diff --git a/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/EJBObjectFactory.java b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/EJBObjectFactory.java new file mode 100644 index 0000000000..119282adb9 --- /dev/null +++ b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/EJBObjectFactory.java @@ -0,0 +1,209 @@ +/* + * 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.tuscany.sca.binding.ejb.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.rmi.RemoteException; +import java.rmi.UnexpectedException; + +import javax.ejb.CreateException; +import javax.ejb.EJBLocalHome; +import javax.naming.NamingException; +import javax.rmi.CORBA.Util; + +import org.omg.CORBA.SystemException; +import org.omg.CORBA.portable.ApplicationException; +import org.omg.CORBA.portable.ObjectImpl; +import org.omg.CORBA.portable.RemarshalException; +import org.omg.stub.java.rmi._Remote_Stub; +import org.oasisopen.sca.ServiceRuntimeException; + +/** + * Factor class that is used to create EJB Proxies. + * + * @version $Rev$ $Date$ + */ +final class EJBObjectFactory { + + private EJBObjectFactory() { + } + + /** + * Get either a generated of dynamic EJB stub using the specified JNDI + * properties. + * <p> + * The returned stub will implement the specified stubInterface Interface. + * If the underlying EJB stub is not assignable from the specified + * stubInterface then a proxy is used to convert between the two. + * <p> + * The returned EJB stub may be either the pregenerated EJB stub or a + * dynamic stub. This allows a client to invoke an EJB without requiring any + * of the pregenerated EJB stub classes be available in the classpath. + * <p> + */ + static Object createStub(NamingEndpoint namingEndpoint, InterfaceInfo ejbInterface) throws NamingException, + RemoteException, CreateException { + + EJBLocator locator = namingEndpoint.getLocator(); + Object homeObject = locator.locate(namingEndpoint.getJndiName()); + /* + * The type of the object returned from the lookup is as follows: If the + * generated stub exists on the classpath, it's an instance of that + * type, otherwise, "org.omg.stub.java.rmi._Remote_Stub" or + * "org.omg.stub.javax.ejb._EJBHome_Stub" + */ + Object stub = getEJBStub(homeObject, ejbInterface); + // Cache dynamic stub only + return stub; + } + + /** + * @param homeObject + * @param ejbHomeClass + * @return + * @throws RemoteException + */ + private static Object getEJBStub(Object homeObject, InterfaceInfo ejbInterface) throws RemoteException, + CreateException { + + Object stub = null; + + // Get the business interface of the EJB + Class ejbInterfaceClass = null; + try { + ejbInterfaceClass = Thread.currentThread().getContextClassLoader().loadClass(ejbInterface.getName()); + } catch (ClassNotFoundException e) { + // ignore + } + + if (ejbInterfaceClass != null && ejbInterfaceClass.isInstance(homeObject)) { + // EJB 3 + stub = homeObject; + } else if (homeObject instanceof EJBLocalHome) { + // Local EJB + stub = createEJBLocalObject(homeObject); + } else { + // Handle dynamic stub + if (homeObject instanceof ObjectImpl) { + ObjectImpl objectImpl = (ObjectImpl)homeObject; + stub = createEJBObject(objectImpl); + }/** + * Above checks will be satisfied if Bean is running on servers like WebSphere. With this + * logic, client (SCA composite with EJB ref binding) doesn't need to include home class or + * client stubs. + * + * Below check is needed SCA composite with EJB ref binding is accessing openEJB implementation. + * For e.g if the bean is running on Geronimo. + */ + else if ((javax.rmi.PortableRemoteObject.narrow(homeObject, javax.ejb.EJBHome.class)) instanceof javax.ejb.EJBHome) { + stub = createEJBObjectFromHome(homeObject); + } else + throw new ServiceRuntimeException("Invalid stub type: " + homeObject.getClass()); + } + return stub; + } + + /** + * Create a pre-generated EJB stub + * + * @param homeObject + * @return + * @throws RemoteException + */ + static private Object createEJBLocalObject(Object homeObject) throws RemoteException { + + Object stub = null; + try { + // Find the "create()" method + Method createMethod = homeObject.getClass().getMethod("create", null); + // Create an EJB object + stub = createMethod.invoke(homeObject, null); + } catch (NoSuchMethodException e) { + // "create()" method not found, it's still a dynamic stub + stub = null; + } catch (InvocationTargetException e) { + throw new RemoteException(e.getTargetException().toString()); + } catch (Exception e) { + throw new RemoteException(e.toString()); + } + return stub; + } + + /** + * Here homeObject is instance of EJBHome + * + * @param homeObject + * @return + * @throws RemoteException + */ + private static Object createEJBObjectFromHome(Object homeObject) throws RemoteException { + + Object stub = null; + try { + // Find the "create()" method + Method createMethod = homeObject.getClass().getMethod("create", null); + // Create an EJB object + stub = createMethod.invoke(homeObject, null); + } catch (NoSuchMethodException e) { + // "create()" method not found, it's still a dynamic stub + stub = null; + } catch (InvocationTargetException e) { + throw new RemoteException(e.getTargetException().toString()); + } catch (Exception e) { + throw new RemoteException(e.toString()); + } + return stub; + } + + /** + * Create an EJBObject using RMI/IIOP APIs + * + * @param ejbHomeObject + * @return The EJBObject remote stub + * @throws CreateException + * @throws RemoteException + */ + private static Object createEJBObject(ObjectImpl ejbHomeObject) throws CreateException, RemoteException { + + try { + org.omg.CORBA_2_3.portable.InputStream in = null; + try { + org.omg.CORBA.portable.OutputStream out = ejbHomeObject._request("create", true); + in = (org.omg.CORBA_2_3.portable.InputStream)ejbHomeObject._invoke(out); + // The Remote stub should be available in JDK + // TODO: [rfeng] Work around an issue in Apache Yoko which doesn't understand the org.omg.stub.* + return in.read_Object(_Remote_Stub.class); + } catch (ApplicationException ex) { + in = (org.omg.CORBA_2_3.portable.InputStream)ex.getInputStream(); + String id = in.read_string(); + if (id.equals("IDL:javax/ejb/CreateEx:1.0")) { + throw (CreateException)in.read_value(CreateException.class); + } + throw new UnexpectedException(id); + } catch (RemarshalException ex) { + return createEJBObject(ejbHomeObject); + } finally { + ejbHomeObject._releaseReply(in); + } + } catch (SystemException ex) { + throw Util.mapSystemException(ex); + } + } +} diff --git a/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/EJBStubHelper.java b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/EJBStubHelper.java new file mode 100644 index 0000000000..72f7dc9e43 --- /dev/null +++ b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/EJBStubHelper.java @@ -0,0 +1,69 @@ +/* + * 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.tuscany.sca.binding.ejb.util; + +import java.rmi.RemoteException; + +import javax.ejb.CreateException; +import javax.naming.NamingException; + +import org.oasisopen.sca.ServiceRuntimeException; +import org.oasisopen.sca.ServiceUnavailableException; + +final class EJBStubHelper { + + private static Object stub; + private static ServiceRuntimeException exception; + + private EJBStubHelper() { + } + + /** + * @param owner + * @param jndiName + * @return + */ + + static Object lookup(NamingEndpoint endpoint, InterfaceInfo ejbInterface) { + return getStub(endpoint, ejbInterface); + } + + private static Object getStub(NamingEndpoint namingEndpoint, InterfaceInfo ejbInterface) { + try { + stub = EJBObjectFactory.createStub(namingEndpoint, ejbInterface); + } catch (NamingException e) { + exception = new ServiceUnavailableException(e); + e.printStackTrace(); + throw (ServiceUnavailableException)exception; + } catch (CreateException e) { + exception = new ServiceUnavailableException(e); + throw (ServiceUnavailableException)exception; + } catch (RemoteException e) { + exception = new ServiceRuntimeException(e); + throw (ServiceRuntimeException)exception; + } + + if (exception == null) { + return stub; // Normal result + } else { + throw exception; // Throw the exception + } + } + +} diff --git a/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/InterfaceInfo.java b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/InterfaceInfo.java new file mode 100644 index 0000000000..e60e526e01 --- /dev/null +++ b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/InterfaceInfo.java @@ -0,0 +1,117 @@ +/* + * 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.tuscany.sca.binding.ejb.util; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.WeakHashMap; + +import org.apache.tuscany.sca.binding.ejb.corba.Java2IDLUtil; + +/** + * The signature for a java interface + * + * @version $Rev$ $Date$ + */ +class InterfaceInfo implements Serializable { + + private static final Map<Class, InterfaceInfo> INTERFACES = + Collections.synchronizedMap(new WeakHashMap<Class, InterfaceInfo>()); + + private static final long serialVersionUID = 2314533906465094860L; + private String name; + + private Map<String, MethodInfo> methods = new HashMap<String, MethodInfo>(); + + InterfaceInfo(final Class iface) { + super(); + if (iface == null) { + throw new IllegalArgumentException("The interface cannot be null"); + } + this.name = iface.getName(); + // SECURITY + /* + * Permission: accessDeclaredMembers : Access denied + * (java.lang.RuntimePermission accessDeclaredMembers) + */ + Map idlNames = AccessController.doPrivileged(new PrivilegedAction<Map>() { + public Map run() { + return Java2IDLUtil.mapMethodToOperation(iface); + } + }); + Iterator i = idlNames.entrySet().iterator(); + while (i.hasNext()) { + Map.Entry entry = (Map.Entry)i.next(); + Method method = (Method)entry.getKey(); + MethodInfo methodInfo = new MethodInfo(method); + methodInfo.setIDLName((String) entry.getValue()); + methods.put(method.getName(), methodInfo); + methods.put(methodInfo.getIDLName(), methodInfo); + } + } + + static final synchronized InterfaceInfo getInstance(final Class iface) { + InterfaceInfo info = (InterfaceInfo)INTERFACES.get(iface); + if (info == null) { + info = new InterfaceInfo(iface); + INTERFACES.put(iface, info); + } + return info; + } + + /** + * @return + */ + Map<String, MethodInfo> getMethods() { + return methods; + } + + /** + * @return + */ + MethodInfo getMethod(String name) { + return (MethodInfo)methods.get(name); + } + + /** + * @return + */ + String getName() { + return name; + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("interface ").append(name).append("{ \n"); + Iterator i = methods.values().iterator(); + while (i.hasNext()) { + MethodInfo methodInfo = (MethodInfo)i.next(); + sb.append("\t").append(methodInfo).append("\n"); + } + sb.append("};\n"); + return sb.toString(); + } +} diff --git a/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/JavaReflectionAdapter.java b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/JavaReflectionAdapter.java new file mode 100644 index 0000000000..651c967aad --- /dev/null +++ b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/JavaReflectionAdapter.java @@ -0,0 +1,157 @@ +/* + * 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.tuscany.sca.binding.ejb.util; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; + +import org.oasisopen.sca.ServiceRuntimeException; + +/** + * An adapter for java classes, indexes the methods by name and provides an + * invoke method that takes a method name. + * + * @version $Rev$ $Date$ + */ +final class JavaReflectionAdapter { + + private static Map<Class, JavaReflectionAdapter> adapters = + Collections.synchronizedMap(new WeakHashMap<Class, JavaReflectionAdapter>()); + + private static final Map<Class, Object> DEFAULT_VALUES = new HashMap<Class, Object>(); + static { + DEFAULT_VALUES.put(boolean.class, Boolean.FALSE); + DEFAULT_VALUES.put(byte.class, new Byte((byte)0)); + DEFAULT_VALUES.put(char.class, new Character((char)0)); + DEFAULT_VALUES.put(short.class, new Short((short)0)); + DEFAULT_VALUES.put(int.class, Integer.valueOf(0)); + DEFAULT_VALUES.put(long.class, new Long(0)); + DEFAULT_VALUES.put(float.class, new Float(0.0)); + DEFAULT_VALUES.put(double.class, new Double(0.0)); + } + + private Map<String, Method> methodMap = new HashMap<String, Method>(); + + /** + * Constructor + * + * @param clazz + */ + private JavaReflectionAdapter(final Class clazz) { + // Index the methods on the implementation class + // FIXME J2 Security - promote this to callers of this method + Method[] methods = AccessController.doPrivileged(new PrivilegedAction<Method[]>() { + public Method[] run() { + return clazz.getMethods(); + } + }); + for (int i = 0; i < methods.length; i++) { + methodMap.put(methods[i].getName(), methods[i]); + } + } + + /** + * Create a java reflection adapter + * + * @param clazz + */ + static synchronized JavaReflectionAdapter createJavaReflectionAdapter(Class clazz) { + JavaReflectionAdapter adapter = (JavaReflectionAdapter)adapters.get(clazz); + if (adapter == null) { + adapter = new JavaReflectionAdapter(clazz); + adapters.put(clazz, adapter); + } + return adapter; + } + + /** + * Return the specified method + * + * @param methodName + * @return + * @throws NoSuchMethodException + */ + Method getMethod(String methodName) throws NoSuchMethodException { + + Method method = (Method)methodMap.get(methodName); + if (method == null) { + throw new NoSuchMethodException(methodName); + } + return method; + } + + /** + * Returns a map containing the methods on the class, keyed by name + * + * @return + */ + Map getMethods() { + return methodMap; + } + + /** + * Invoke a method using Java reflection. + * + * @param method + * @param object + * @param args + * @return + * @throws InvocationTargetException + * @throws IllegalAccessException + */ + Object invoke(Method method, Object object, Object[] args) throws InvocationTargetException, + IllegalAccessException { + Class[] parameterTypes = method.getParameterTypes(); + for (int i = 0; i < parameterTypes.length; i++) { + Class parameterType = parameterTypes[i]; + if (args[i] == null && parameterType.isPrimitive()) { + args[i] = DEFAULT_VALUES.get(parameterType); + } + } + return method.invoke(object, args); + } + + /** + * Set the java bean property + * + * @param bean + * @param propertyName + * @param value + * @return + */ + boolean setProperty(Object bean, String propertyName, Object value) { + try { + PropertyDescriptor propertyDescriptor = new PropertyDescriptor(propertyName, bean.getClass()); + Method writeMethod = propertyDescriptor.getWriteMethod(); + writeMethod.invoke(bean, new Object[] {value}); + return true; + } catch (InvocationTargetException e) { + throw new ServiceRuntimeException(e.getTargetException()); + } catch (Exception e) { + return false; + } + } +} diff --git a/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/MethodInfo.java b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/MethodInfo.java new file mode 100644 index 0000000000..e7ea7c9077 --- /dev/null +++ b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/MethodInfo.java @@ -0,0 +1,189 @@ +/* + * 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.tuscany.sca.binding.ejb.util; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +/** + * MetaData for a java method + * + * @version $Rev$ $Date$ + */ +class MethodInfo implements Serializable { + + /** Automatically generated javadoc for: serialVersionUID */ + private static final long serialVersionUID = -5557260979514687514L; + private String name; + private String returnType; + private String[] parameterTypes; + private String[] exceptionTypes; + + private String IDLName; + + private transient Method method; + + /** + * Type Signature Java Type -------------- --------- Z boolean B byte C char + * S short I int J long F float D double L fully-qualified-class ; + * fully-qualified-class [ type type[] ( arg-types ) ret-type method type + */ + private static final Map signatures = new HashMap(); + static { + signatures.put("Z", boolean.class); + signatures.put("B", byte.class); + signatures.put("C", char.class); + signatures.put("S", short.class); + signatures.put("I", int.class); + signatures.put("J", long.class); + signatures.put("F", float.class); + signatures.put("D", double.class); + signatures.put("V", void.class); + } + + MethodInfo(Method method) { + this.method = method; + this.name = method.getName(); + // this.declaringClass = method.getDeclaringClass().getName(); + this.returnType = method.getReturnType().getName(); + Class[] types = method.getParameterTypes(); + this.parameterTypes = new String[types.length]; + for (int i = 0; i < types.length; i++) { + this.parameterTypes[i] = types[i].getName(); + } + types = method.getExceptionTypes(); + this.exceptionTypes = new String[types.length]; + for (int i = 0; i < types.length; i++) { + this.exceptionTypes[i] = types[i].getName(); + } + IDLName = this.name; + } + + MethodInfo(String name, String returnType, String[] parameterTypes, String[] exceptionTypes) { + this.name = name; + this.returnType = returnType; + this.parameterTypes = parameterTypes; + this.exceptionTypes = exceptionTypes; + this.IDLName = name; + } + + /** + * Parse the class name from the internal signature Sample signatures: int + * ---> I; int[] ---> [I Object ---> java/lang/Object Object[] ---> + * [Ljava/lang/Object; + * + * @param value + * @return + */ + private static String getName(String signature) { + String name = signature; + // Remove leading ARRAY ([) signatures + int index = name.lastIndexOf('['); + if (index != -1) + name = name.substring(index + 1); + + // Remove L<...>; + if (name.charAt(0) == 'L' && name.charAt(name.length() - 1) == ';') + name = name.substring(1, name.length() - 1); + + // Primitive types + Class primitiveClass = (Class)signatures.get(name); + if (primitiveClass != null) { + name = primitiveClass.getName(); + } + + for (int i = 0; i < index + 1; i++) { + name = name + "[]"; + } + return name; + } + + /** + * @return + */ + String getName() { + return name; + } + + /** + * @return + */ + String[] getParameterTypes() { + return parameterTypes; + } + + /** + * @return + */ + String getReturnType() { + return returnType; + } + + /** + * @return + */ + String[] getExceptionTypes() { + return exceptionTypes; + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append(getName(returnType)).append(" ").append(name).append("("); + for (int j = 0; j < parameterTypes.length; j++) { + sb.append(getName(parameterTypes[j])).append(" ").append("arg" + j); + if (j < (parameterTypes.length - 1)) + sb.append(", "); + } + sb.append(")"); + if (exceptionTypes.length > 0) { + sb.append(" throws "); + for (int k = 0; k < exceptionTypes.length; k++) { + sb.append(exceptionTypes[k]); + if (k < (exceptionTypes.length - 1)) + sb.append(", "); + } + } + sb.append(";"); + return sb.toString(); + } + + /** + * @return Returns the iDLName. + */ + String getIDLName() { + return IDLName; + } + + /** + * @param name The iDLName to set. + */ + void setIDLName(String name) { + IDLName = name; + } + + /** + * @return the method + */ + Method getMethod() { + return method; + } +} diff --git a/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/NamingEndpoint.java b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/NamingEndpoint.java new file mode 100644 index 0000000000..346b7c5cf6 --- /dev/null +++ b/branches/2.0-M2/modules/binding-ejb-runtime/src/main/java/org/apache/tuscany/sca/binding/ejb/util/NamingEndpoint.java @@ -0,0 +1,124 @@ +/* + * 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.tuscany.sca.binding.ejb.util; + +import java.security.AccessController; +import java.security.PrivilegedAction; + +public class NamingEndpoint { + private String jndiName; + private EJBLocator locator; + private boolean managed = true; + + public NamingEndpoint(String hostName, int port, String jndiName) { + this.jndiName = jndiName; + this.locator = new EJBLocator(hostName, port); + } + + public NamingEndpoint(String name) { + + /** + * by default it's a managed environment means SCA composite with ref + * binding is running on an AppServer. If running on J2SE, pass + * -Dmanaged=false for the VM + */ + final String managedEnv = AccessController.doPrivileged(new PrivilegedAction<String>() { + public String run() { + return System.getProperty("managed"); + } + }); + + if (managedEnv != null) { + managed = Boolean.valueOf(managedEnv); + } + + if ((!managed) && name.startsWith("corbaname:iiop:")) { + /** + * if (name.startsWith("corbaname:iiop:")) { corbaname:iiop:<hostName>:<port>/<root>#name + * For example, + * "corbaname:iiop:localhost:2809/NameServiceServerRoot#ejb/MyEJBHome"; + */ + + String[] parts = split(name, '#'); + if (parts.length != 2) { + throw new IllegalArgumentException("Invalid corbaname: " + name); + } + + this.jndiName = name; // The logical JNDI name + this.locator = new EJBLocator(parts[0], managed); + + } else { + this.jndiName = name; + this.locator = new EJBLocator(managed); + } + + } + + private static String[] split(String str, char ch) { + int index = str.lastIndexOf(ch); + if (index == -1) { + return new String[] {str, ""}; + } else { + return new String[] {str.substring(0, index), str.substring(index + 1)}; + } + } + + /** + * @return Returns the jndiName. + */ + public String getJndiName() { + return jndiName; + } + + public EJBLocator getLocator() { + return locator; + } + + public String getCorbaname() { + return locator.getCorbaname(jndiName); + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (obj instanceof NamingEndpoint) { + NamingEndpoint endpoint = (NamingEndpoint)obj; + return jndiName.equals(endpoint.jndiName); + } + return false; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return jndiName.hashCode(); + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return jndiName; + } +} |