/* * Copyright 2009-2010 Brian S O'Neill * * Licensed 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.cojen.dirmi; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.rmi.Remote; import java.rmi.RemoteException; import org.junit.*; import static org.junit.Assert.*; import org.cojen.classfile.*; /** * * * @author Brian S O'Neill */ public class TestMismatchedInterface { public static void main(String[] args) { org.junit.runner.JUnitCore.main(TestMismatchedInterface.class.getName()); } private Environment mEnv; @Before public void setup() { mEnv = new Environment(); } @After public void tearDown() throws Exception { mEnv.close(); } @Test public void interfaceMismatch() throws Exception { // Define two remote interfaces with same name, but with different // methods. This requires that they are defined with separate // ClassLoaders. Class iface0, server0; { RuntimeClassFile cf = new RuntimeClassFile("IFace", null, new Loader(), null, true); cf.setModifiers(Modifiers.PUBLIC.toInterface(true)); cf.addInterface(Remote.class); TypeDesc exType = TypeDesc.forClass(RemoteException.class); cf.addMethod(Modifiers.PUBLIC_ABSTRACT, "a", TypeDesc.STRING, null) .addException(exType); cf.addMethod(Modifiers.PUBLIC_ABSTRACT, "b", TypeDesc.STRING, null) .addException(exType); iface0 = cf.defineClass(); cf = new RuntimeClassFile(null, null, iface0.getClassLoader()); cf.addInterface(iface0); cf.addDefaultConstructor(); CodeBuilder b = new CodeBuilder (cf.addMethod(Modifiers.PUBLIC, "a", TypeDesc.STRING, null)); b.loadConstant("a"); b.returnValue(TypeDesc.STRING); b = new CodeBuilder(cf.addMethod(Modifiers.PUBLIC, "b", TypeDesc.STRING, null)); b.loadConstant("b"); b.returnValue(TypeDesc.STRING); server0 = cf.defineClass(); } Class iface1, server1; { RuntimeClassFile cf = new RuntimeClassFile("IFace", null, new Loader(), null, true); cf.setModifiers(Modifiers.PUBLIC.toInterface(true)); cf.addInterface(Remote.class); TypeDesc exType = TypeDesc.forClass(RemoteException.class); cf.addMethod(Modifiers.PUBLIC_ABSTRACT, "b", TypeDesc.STRING, null) .addException(exType); cf.addMethod(Modifiers.PUBLIC_ABSTRACT, "c", TypeDesc.STRING, null) .addException(exType); iface1 = cf.defineClass(); cf = new RuntimeClassFile(null, null, iface1.getClassLoader()); cf.addInterface(iface1); cf.addDefaultConstructor(); CodeBuilder b = new CodeBuilder (cf.addMethod(Modifiers.PUBLIC, "b", TypeDesc.STRING, null)); b.loadConstant("b"); b.returnValue(TypeDesc.STRING); b = new CodeBuilder(cf.addMethod(Modifiers.PUBLIC, "c", TypeDesc.STRING, null)); b.loadConstant("c"); b.returnValue(TypeDesc.STRING); server1 = cf.defineClass(); } Object obj1 = server0.newInstance(); Object obj2 = server1.newInstance(); Session[] pair = mEnv.newSessionPair(); pair[0].setClassLoader(iface0.getClassLoader()); pair[1].setClassLoader(iface1.getClassLoader()); pair[0].send(obj1); pair[1].send(obj2); Object remote0 = pair[0].receive(); Object remote1 = pair[1].receive(); assertTrue(iface0.isInstance(remote0)); Method a0 = remote0.getClass().getMethod("a", (Class[]) null); try { a0.invoke(remote0, (Object[]) null); fail(); } catch (InvocationTargetException e) { assertTrue(e.getCause() instanceof UnimplementedMethodException); } Method b0 = remote0.getClass().getMethod("b", (Class[]) null); assertEquals("b", b0.invoke(remote0, (Object[]) null)); Method ib0 = iface0.getMethod("b", (Class[]) null); assertEquals("b", ib0.invoke(remote0, (Object[]) null)); // Not part of interface, but available via reflection. Method c0 = remote0.getClass().getMethod("c", (Class[]) null); assertEquals("c", c0.invoke(remote0, (Object[]) null)); // Try again with other endpoint. assertTrue(iface1.isInstance(remote1)); // Not part of interface, but available via reflection. Method a1 = remote1.getClass().getMethod("a", (Class[]) null); assertEquals("a", a1.invoke(remote1, (Object[]) null)); Method b1 = remote1.getClass().getMethod("b", (Class[]) null); assertEquals("b", b1.invoke(remote1, (Object[]) null)); Method ib1 = iface1.getMethod("b", (Class[]) null); assertEquals("b", ib1.invoke(remote1, (Object[]) null)); Method c1 = remote1.getClass().getMethod("c", (Class[]) null); try { c1.invoke(remote1, (Object[]) null); fail(); } catch (InvocationTargetException e) { assertTrue(e.getCause() instanceof UnimplementedMethodException); } } @Test public void missingInterface() throws Exception { // Client doesn't have the interface provided by server, so it just // uses plain Remote interface. Class iface, server; { RuntimeClassFile cf = new RuntimeClassFile("MyOwn", null, new Loader(), null, true); cf.setModifiers(Modifiers.PUBLIC.toInterface(true)); cf.addInterface(Remote.class); TypeDesc exType = TypeDesc.forClass(RemoteException.class); cf.addMethod(Modifiers.PUBLIC_ABSTRACT, "foo", TypeDesc.STRING, null) .addException(exType); iface = cf.defineClass(); cf = new RuntimeClassFile(null, null, iface.getClassLoader()); cf.addInterface(iface); cf.addDefaultConstructor(); CodeBuilder b = new CodeBuilder (cf.addMethod(Modifiers.PUBLIC, "foo", TypeDesc.STRING, null)); b.loadConstant("foo"); b.returnValue(TypeDesc.STRING); server = cf.defineClass(); } Object obj = server.newInstance(); Session[] pair = mEnv.newSessionPair(); pair[0].send(obj); Object remote = pair[1].receive(); assertTrue(remote instanceof Remote); assertFalse(iface.isInstance(remote)); Method foo = remote.getClass().getMethod("foo", (Class[]) null); assertEquals("foo", foo.invoke(remote, (Object[]) null)); } @Test public void commonParent() throws Exception { // Client doesn't have the interface provided by server, but it does // have the parent interface. Class iface, server; { RuntimeClassFile cf = new RuntimeClassFile("Extended", null, new Loader(), null, true); cf.setModifiers(Modifiers.PUBLIC.toInterface(true)); cf.addInterface(Parent.class); TypeDesc exType = TypeDesc.forClass(RemoteException.class); cf.addMethod(Modifiers.PUBLIC_ABSTRACT, "extraName", TypeDesc.STRING, null) .addException(exType); iface = cf.defineClass(); cf = new RuntimeClassFile(null, null, iface.getClassLoader()); cf.addInterface(iface); cf.addDefaultConstructor(); CodeBuilder b = new CodeBuilder (cf.addMethod(Modifiers.PUBLIC, "name", TypeDesc.STRING, null)); b.loadConstant("bob"); b.returnValue(TypeDesc.STRING); b = new CodeBuilder (cf.addMethod(Modifiers.PUBLIC, "extraName", TypeDesc.STRING, null)); b.loadConstant("extra"); b.returnValue(TypeDesc.STRING); server = cf.defineClass(); } Object obj = server.newInstance(); Session[] pair = mEnv.newSessionPair(); pair[0].send(obj); Object remote = pair[1].receive(); assertTrue(remote instanceof Remote); assertTrue(remote instanceof Parent); assertFalse(iface.isInstance(remote)); Method extra = remote.getClass().getMethod("extraName", (Class[]) null); assertEquals("extra", extra.invoke(remote, (Object[]) null)); assertEquals("bob", ((Parent) remote).name()); } @Test public void commonParentMismatch() throws Exception { // Client doesn't have the interface provided by server, but it does // have a mismatched parent interface. final String parentName = Parent.class.getName(); class CL extends Loader { @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { if (name.equals(parentName)) { throw new ClassNotFoundException(); } return super.loadClass(name, resolve); } } Class iface, server; { RuntimeClassFile cf = new RuntimeClassFile(parentName, null, new CL(), null, true); cf.setModifiers(Modifiers.PUBLIC.toInterface(true)); cf.addInterface(Remote.class); TypeDesc exType = TypeDesc.forClass(RemoteException.class); cf.addMethod(Modifiers.PUBLIC_ABSTRACT, "name", TypeDesc.INT, null) .addException(exType); Class parent = cf.defineClass(); cf = new RuntimeClassFile("Extended", null, parent.getClassLoader(), null, true); cf.setModifiers(Modifiers.PUBLIC.toInterface(true)); cf.addInterface(parent); cf.addMethod(Modifiers.PUBLIC_ABSTRACT, "extraName", TypeDesc.STRING, null) .addException(exType); iface = cf.defineClass(); cf = new RuntimeClassFile(null, null, iface.getClassLoader()); cf.addInterface(iface); cf.addDefaultConstructor(); CodeBuilder b = new CodeBuilder (cf.addMethod(Modifiers.PUBLIC, "name", TypeDesc.STRING, null)); b.loadConstant("bob"); b.returnValue(TypeDesc.STRING); b = new CodeBuilder (cf.addMethod(Modifiers.PUBLIC, "extraName", TypeDesc.STRING, null)); b.loadConstant("extra"); b.returnValue(TypeDesc.STRING); server = cf.defineClass(); } Object obj = server.newInstance(); Session[] pair = mEnv.newSessionPair(); pair[0].send(obj); Object remote = pair[1].receive(); assertTrue(remote instanceof Remote); assertTrue(remote instanceof Parent); assertFalse(iface.isInstance(remote)); Method extra = remote.getClass().getMethod("extraName", (Class[]) null); assertEquals("extra", extra.invoke(remote, (Object[]) null)); try { // Return type differs. assertEquals("bob", ((Parent) remote).name()); fail(); } catch (UnimplementedMethodException e) { } } private static class Loader extends ClassLoader { Loader() { super(TestMismatchedInterface.class.getClassLoader()); } } public static interface Parent extends Remote { String name() throws Exception; } }