/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.openejb.assembler.classic; import java.lang.reflect.Method; import java.rmi.Remote; import java.util.ArrayList; import java.util.List; /** * @version $Rev$ $Date$ */ public class ProxyInterfaceResolver { public static List<Class> getInterfaces(final Class implementation, final Class mainInterface, final List<Class> interfaces) { final List<Class> valid = new ArrayList<Class>(); // The intended interface is safe to add if (mainInterface != null) { valid.add(mainInterface); } // Any interface the bean implements is safe (potentially) for (final Class interfce : interfaces) { if (interfce.isAssignableFrom(implementation)) { valid.add(interfce); } } // Here comes the trick, if any of them implement java.rmi.Remote // we have to check the "remote" methods against the other methods // of the same name and params to see if there are any conflicts in // the exception clauses. If there are, then we need to remove the // conflicting interface(s). // // DETAILS: // The trick is that two nearly matching interface methods // -InterfaceA: void doIt() throws Foo; // -InterfaceB: void doIt() throws Bar; // // can be implemented in a class by leaving out exceptions from the // throws clause that aren't declared in both interfaces methods. // -Implementation: void doIt(){} // // This means the implementing class can not throw Foo or Bar. The // same rule applies to proxies created from these two interfaces as // the proxy generating code will automatically remove exceptions // not shared by the two matching interface methods; eliminating // the proxy's and therefore container's ability to throw those // exceptions. // // The specific issue with java.rmi.Remote interfaces is that per // spec rules many runtime exceptions (container or connection issues) // are thrown to clients as java.rmi.RemoteException which is not // a runtime exception and must be throwable via the proxy. final List<Class> remotes = new ArrayList<Class>(); final List<Class> nonremotes = new ArrayList<Class>(); for (final Class interfce : valid) { if (Remote.class.isAssignableFrom(interfce)) { remotes.add(interfce); } else { nonremotes.add(interfce); } } // No remote interfaces, we're good to go if (remotes.size() == 0) { return valid; } // ----------------------------------------------------------- // If we got here, we have potentially clashing interfaces // We sort of have to start over and go "slower" checking // methods for conflicts and not including those interfaces // ----------------------------------------------------------- valid.clear(); remotes.remove(mainInterface); nonremotes.remove(mainInterface); // Re-add the main interface valid.add(mainInterface); // Track the method signatures of the interfaces we add final List<Signature> proxySignatures = getSignatures(mainInterface); // Show affinity for the remote interfaces if the main // interface is a java.rmi.Remote if (Remote.class.isAssignableFrom(mainInterface)) { for (final Class interfce : remotes) { addIfNotConflicting(interfce, valid, proxySignatures); } for (final Class interfce : nonremotes) { addIfNotConflicting(interfce, valid, proxySignatures); } } else { for (final Class interfce : nonremotes) { addIfNotConflicting(interfce, valid, proxySignatures); } for (final Class interfce : remotes) { addIfNotConflicting(interfce, valid, proxySignatures); } } return valid; } /** * Adds the interface to the list of valid interfaces for the proxy * if the signatures on the interface do not conflict with the method * signatures already apart of the proxy's other interfaces. * * @param interfce * @param valid * @param proxySignatures */ private static void addIfNotConflicting(final Class interfce, final List<Class> valid, final List<Signature> proxySignatures) { final List<Signature> interfaceSigs = getSignatures(interfce); for (final Signature sig : interfaceSigs) { // Contains will return true if the // method signature exits *and* has // a different throws clause if (proxySignatures.contains(sig)) { return; // conflicts and cannot be added } } // Does not conflict, add it and track the new signatures valid.add(interfce); proxySignatures.addAll(interfaceSigs); } private static List<Signature> getSignatures(final Class mainInterface) { final List<Signature> sigs = new ArrayList<Signature>(); for (final Method method : mainInterface.getMethods()) { sigs.add(new Signature(mainInterface, method)); } return sigs; } public static class Signature { private final Class clazz; private final Method method; private final String sig; public Signature(final Class clazz, final Method method) { this.clazz = clazz; this.method = method; final StringBuilder sb = new StringBuilder(); sb.append(method.getName()); sb.append('('); for (final Class<?> type : method.getParameterTypes()) { sb.append(type.getName()); sb.append(','); } sb.append(')'); sig = sb.toString(); } public Method getMethod() { return method; } // This equals returns true only if the method signatures // are the same *and* one is remote and one is not public boolean equals(final Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } final Signature signature = (Signature) o; if (!sig.equals(signature.sig)) { return false; } final boolean aIsRemote = Remote.class.isAssignableFrom(clazz); final boolean bIsRemote = Remote.class.isAssignableFrom(signature.clazz); return !(aIsRemote == bIsRemote); } public int hashCode() { return sig.hashCode(); } public String toString() { return method.toString(); } } }