/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package test.implementation.util;

import java.util.Set;

import java.lang.reflect.Method;

import javax.management.Attribute;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.ObjectInstance;
import javax.management.ObjectName;

import javax.management.modelmbean.ModelMBean;
import javax.management.modelmbean.RequiredModelMBean;

import test.implementation.util.support.Trivial;
import test.implementation.util.support.TrivialMBean;
import test.implementation.util.support.Trivial2;
import test.implementation.util.support.Trivial2MBean;
import test.implementation.util.support.ExtendedResource;
import test.implementation.util.support.MyInterface;
import test.implementation.util.support.MyInterface2;
import test.implementation.util.support.Resource;
import test.implementation.util.support.ResourceOverride;
import test.implementation.util.support.ResourceIncorrectInfo;

import junit.framework.TestCase;

import org.jboss.mx.util.AgentID;
import org.jboss.mx.util.DefaultExceptionHandler;
import org.jboss.mx.util.MBeanProxy;
import org.jboss.mx.util.ProxyContext;

import org.jboss.mx.modelmbean.XMBean;


/**
 * Tests for mbean proxy
 *
 * @author  <a href="mailto:juha@jboss.org">Juha Lindfors</a>.
 * @version $Revision$ 
 */
public class MBeanProxyTEST extends TestCase
{
   public MBeanProxyTEST(String s)
   {
      super(s);
   }

   public void testGetWithServer() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      ObjectName oname   = new ObjectName("test:name=test");
   
      server.registerMBean(new Trivial(), oname);
   
      TrivialMBean mbean = (TrivialMBean)MBeanProxy.get(
            TrivialMBean.class, oname, server);      
            
      mbean.doOperation();
   }
   
   public void testGetWithAgentID() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      String agentID     = AgentID.get(server);
      ObjectName oname   = new ObjectName("test:name=test");
      
      server.registerMBean(new Trivial(), oname);

      TrivialMBean mbean = (TrivialMBean)MBeanProxy.get(
            TrivialMBean.class, oname, agentID);
            
      mbean.doOperation();
   }
   
   public void testCreateWithServer() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      ObjectName oname   = new ObjectName("test:test=test");
      
      TrivialMBean mbean = (TrivialMBean)MBeanProxy.create(
            Trivial.class, TrivialMBean.class, oname, server);
            
      mbean.doOperation();
   }
   
   public void testCreateWithAgentID() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      ObjectName oname   = new ObjectName("test:test=test");
      String agentID     = AgentID.get(server);
      
      TrivialMBean mbean = (TrivialMBean)MBeanProxy.create(
            Trivial.class, TrivialMBean.class, oname, agentID);
            
      mbean.doOperation();
   }
   
   public void testProxyInvocations() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      ObjectName oname   = new ObjectName("test:name=test");
      
      server.registerMBean(new Trivial(), oname);
      
      TrivialMBean mbean = (TrivialMBean)MBeanProxy.get(
            TrivialMBean.class, oname, AgentID.get(server));
      
      mbean.doOperation();
      mbean.setSomething("JBossMX");
      
      assertEquals("JBossMX", mbean.getSomething());
   }

   public void testProxyInvocationWithConflictingMBeanAndContextMethods() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      ObjectName oname   = new ObjectName("test:test=test");
      
      server.registerMBean(new Trivial(), oname);
      
      TrivialMBean mbean = (TrivialMBean)MBeanProxy.get(
            TrivialMBean.class, oname, AgentID.get(server));
            
      mbean.getMBeanServer();
      assertTrue(mbean.isGMSInvoked());
   }
   
   public void testContextAccess() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      ObjectName oname   = new ObjectName("test:test=test");
      
      Trivial2MBean mbean = (Trivial2MBean)MBeanProxy.create(
            Trivial2.class, Trivial2MBean.class, oname, server
      );
    
      ProxyContext ctx = (ProxyContext)mbean;  
      
      ctx.getMBeanServer();
   }
   
   public void testProxyInvocationBetweenServers() throws Exception
   {
      MBeanServer server1 = MBeanServerFactory.createMBeanServer();
      MBeanServer server2 = MBeanServerFactory.createMBeanServer();
      ObjectName oname1   = new ObjectName("test:name=target");
      ObjectName oname2   = new ObjectName("test:name=proxy");
      
      // createMBean on server1 and retrieve a proxy to it
      Trivial2MBean mbean = (Trivial2MBean)MBeanProxy.create(
            Trivial2.class, Trivial2MBean.class, oname1, server1
      );
      
      //bind the proxy to server2
      server2.registerMBean(mbean, oname2);
      
      // invoke on server2
      server2.invoke(oname2, "doOperation", null, null);
      
      // check that server1 received the invocation
      assertTrue(((Boolean)server1.getAttribute(oname1, "OperationInvoked")).booleanValue());
   }
   
   public void testSimultaneousTypedAndDetypedInvocations() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      ObjectName oname   = new ObjectName("test:test=test");
      
      Trivial2MBean mbean = (Trivial2MBean)MBeanProxy.create(
            Trivial2.class, Trivial2MBean.class, oname,server
      );
      
      // typed proxy interface
      mbean.setSomething("Kissa");
      assertTrue(mbean.getSomething().equals("Kissa"));
      
      // detyped proxy interface
      DynamicMBean mbean2 = (DynamicMBean)mbean;
      mbean2.setAttribute(new Attribute("Something", "Koira"));
      assertTrue(mbean2.getAttribute("Something").equals("Koira"));
      
      // direct local server invocation
      server.setAttribute(oname, new Attribute("Something", "Kissa"));
      assertTrue(server.getAttribute(oname, "Something").equals("Kissa"));
            
      // typed proxy interface invocation
      mbean.doOperation();
      assertTrue(mbean.isOperationInvoked());
      
      mbean.reset();
      
      // detyped proxy invocation
      mbean2.invoke("doOperation", null, null);
      assertTrue(((Boolean)mbean2.getAttribute("OperationInvoked")).booleanValue());
      
      mbean2.invoke("reset", null, null);
      
      // direct local server invocation
      server.invoke(oname, "doOperation", null, null);
      assertTrue(((Boolean)server.getAttribute(oname, "OperationInvoked")).booleanValue());
   }
   
   public void testContextAccessToMBeanServer() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      ObjectName oname   = new ObjectName("test:test=test");
      
      Trivial2MBean mbean = (Trivial2MBean)MBeanProxy.create(
            Trivial2.class, Trivial2MBean.class, oname, server
      );
      
      // query the server this mbean is registered to
      ProxyContext ctx = (ProxyContext)mbean;
      MBeanServer srvr = ctx.getMBeanServer();
      
      Set mbeans = srvr.queryMBeans(new ObjectName("test:*"), null);
      ObjectInstance oi = (ObjectInstance)mbeans.iterator().next();
      
      assertTrue(oi.getObjectName().equals(oname));
      
      assertTrue(srvr.getAttribute(
            new ObjectName("JMImplementation:type=MBeanServerDelegate"),
            "ImplementationName"
      ).equals("JBossMX"));
      
   }
   
   public void testArbitraryInterfaceWithProxy() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      ObjectName oname   = new ObjectName("test:test=test");
    
      RequiredModelMBean rmm = new RequiredModelMBean();
      Resource resource      = new Resource();
      
      rmm.setManagedResource(resource, "ObjectReference");
      rmm.setModelMBeanInfo(resource.getMBeanInfo());
    
      server.registerMBean(rmm, oname);
      
      MyInterface mbean  = (MyInterface)MBeanProxy.get(
            MyInterface.class, oname, server
      );
      
      mbean.setAttributeName("foo");
      mbean.setAttributeName2("bar");
      
      assertTrue(mbean.getAttributeName2().equals("bar"));
      assertTrue(mbean.doOperation().equals("tamppi"));
   }
   
   /**
    * This test shows how to override the default exception handling for
    * proxy invocations. The default handling for exceptions that are not
    * declared as part of the proxy typed interface (such as InstanceNotFound
    * or AttributeNotFound exceptions) are rethrown as unchecked 
    * RuntimeProxyExceptions. See org.jboss.mx.proxy.DefaultExceptionHandler
    * for details. This behavior can be changed at runtime through the proxy
    * context interface setExceptionHandler() method.
    */
   public void testCustomExceptionHandler() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      ObjectName oname   = new ObjectName("test:test=test");
      ObjectName oname2  = new ObjectName("test:test=test2");
      
      RequiredModelMBean rmm = new RequiredModelMBean();
      Resource resource      = new Resource();
      
      rmm.setManagedResource(resource, "ObjectReference");
      rmm.setModelMBeanInfo(resource.getMBeanInfo());
      
      // create two identical mbeans
      server.registerMBean(rmm, oname);
      server.registerMBean(rmm, oname2);
      
      ProxyContext ctx  = (ProxyContext)MBeanProxy.get(
            MyInterface.class, oname, server
      );

      // override InstanceNotFound exception to redirect from test=test instance
      // to test=test2 instance
      ctx.setExceptionHandler(new DefaultExceptionHandler() 
      {
         public Object handleInstanceNotFound(ProxyContext proxyCtx, InstanceNotFoundException e, Method m, Object[] args) throws Exception
         {
            return proxyCtx.getMBeanServer().invoke(new ObjectName("test:test=test2"), m.getName(), args, null);
         }
      });
         
      // unregister mbean 1
      server.unregisterMBean(oname);
      
      // invocation attempt to mbean1 should now redirect to mbean2
      MyInterface mbean = (MyInterface)ctx;
      assertTrue(mbean.doOperation().equals("tamppi"));      
   }
   
   public void testObjectToStringOnProxy() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      ObjectName oname   = new ObjectName("test:test=test");
      
      RequiredModelMBean rmm = new RequiredModelMBean();
      Resource resource      = new Resource();
      
      rmm.setManagedResource(resource, "ObjectReference");
      rmm.setModelMBeanInfo(resource.getMBeanInfo());
      
      server.registerMBean(rmm, oname);
      
      MyInterface mbean = (MyInterface)MBeanProxy.get(
            MyInterface.class, oname, server
      );
      
      mbean.toString();
      
      Object o = (Object)mbean;
      o.toString();
   }
   
   public void testObjectToStringOverride() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      ObjectName oname   = new ObjectName("test:test=test");
      
      RequiredModelMBean rmm = new RequiredModelMBean();
      ResourceOverride resource = new ResourceOverride();
      
      rmm.setManagedResource(resource, "ObjectReference");
      rmm.setModelMBeanInfo(resource.getMBeanInfo());
      
      server.registerMBean(rmm, oname);
      
      MyInterface mbean = (MyInterface)MBeanProxy.get(
            MyInterface.class, oname, server
      );
      
      assertTrue(mbean.toString().equals("Resource"));
      Object o = (Object)mbean;
      assertTrue(o.toString().equals("Resource"));
   }
   
   public void testObjectHashCodeOnProxy() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      ObjectName oname   = new ObjectName("test:test=test");
      
      RequiredModelMBean rmm = new RequiredModelMBean();
      Resource resource      = new Resource();
      
      rmm.setManagedResource(resource, "ObjectReference");
      rmm.setModelMBeanInfo(resource.getMBeanInfo());
      
      server.registerMBean(rmm, oname);
      
      MyInterface mbean = (MyInterface)MBeanProxy.get(
            MyInterface.class, oname, server
      );
      
      mbean.hashCode();
      
      Object o = (Object)mbean;
      o.toString();
   }
   
   public void testObjectHashCodeOverride() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      ObjectName oname   = new ObjectName("test:test=test");
      
      RequiredModelMBean rmm = new RequiredModelMBean();
      ResourceOverride resource = new ResourceOverride();
      
      rmm.setManagedResource(resource, "ObjectReference");
      rmm.setModelMBeanInfo(resource.getMBeanInfo());
      
      server.registerMBean(rmm, oname);
      
      MyInterface mbean = (MyInterface)MBeanProxy.get(
            MyInterface.class, oname, server
      );

      assertTrue(mbean.hashCode() == 10);
      Object o = (Object)mbean;
      assertTrue(o.hashCode() == 10);
   }

   public void testObjectEqualsOnProxy() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      ObjectName oname   = new ObjectName("test:test=test");

      ModelMBean mmbean = new XMBean();
      Resource resource = new Resource();

      mmbean.setManagedResource(resource, "ObjectReference");
      mmbean.setModelMBeanInfo(resource.getMBeanInfo());

      server.registerMBean(mmbean, oname);

      MyInterface mbean = (MyInterface)MBeanProxy.get(
            MyInterface.class, oname, server
      );
      
      MyInterface mbean2 = (MyInterface)MBeanProxy.get(
            MyInterface.class, oname, server
      );
      
      assertTrue(mbean.equals(mbean));
      assertTrue(!mbean.equals(mbean2));
      assertTrue(!mbean2.equals(mbean));
   }
   
   public void testObjectEqualsOverride() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      ObjectName oname   = new ObjectName("test:test=test");
      
      ModelMBean rmm = new RequiredModelMBean();
      ResourceOverride resource  = new ResourceOverride("state");
      
      rmm.setManagedResource(resource, "ObjectReference");
      rmm.setModelMBeanInfo(resource.getMBeanInfo());
      
      server.registerMBean(rmm, oname);
            
      MyInterface mbean = (MyInterface)MBeanProxy.get(
            MyInterface.class, oname, server
      );
      
      assertTrue(mbean.equals(mbean));
   }
   
   public void testAttributeNotFoundOnTypedProxy() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      ObjectName oname   = new ObjectName("test:test=test");
      
      ModelMBean mmbean = new XMBean();
      ResourceIncorrectInfo resource = new ResourceIncorrectInfo();
      
      mmbean.setManagedResource(resource, "ObjectReference");
      mmbean.setModelMBeanInfo(resource.getMBeanInfo());
      
      server.registerMBean(mmbean, oname);
      
      MyInterface mbean = (MyInterface)MBeanProxy.get(
            MyInterface.class, oname, server
      );
      
      ProxyContext ctx = (ProxyContext)mbean;
      ctx.setExceptionHandler(new DefaultExceptionHandler());
      
      try
      {
         mbean.setAttributeName2("some name");
      }
      catch (IllegalArgumentException e)
      {
         // expected
         
         // by default, if no such attribute 'AttributeName2' exists in the
         // MBean metadata (as is the case with ResourceIncorrectInfo), the
         // MBeanProxy invocation handler falls back attempting to execute
         // MBeanServer.invoke() which fails with IAE.
      }
   }

   public void testAttributeNotFoundOnDeTypedProxy() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      ObjectName oname   = new ObjectName("test:test=test");
      
      ModelMBean mmbean = new XMBean();
      ResourceIncorrectInfo resource = new ResourceIncorrectInfo();
      
      mmbean.setManagedResource(resource, "ObjectReference");
      mmbean.setModelMBeanInfo(resource.getMBeanInfo());
      
      server.registerMBean(mmbean, oname);
      
      DynamicMBean mbean = (DynamicMBean)MBeanProxy.get(oname, server);
      
      ProxyContext ctx = (ProxyContext)mbean;
      ctx.setExceptionHandler(new DefaultExceptionHandler());
      
      try
      {
         mbean.setAttribute(new Attribute("AttributeName2", "some name"));
      }
      catch (AttributeNotFoundException e)
      {
         // expected
      }
   }
   
   public void testInheritanceInTypedProxyArgs() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      ObjectName oname   = new ObjectName("test:test=test");
      
      XMBean mmbean = new XMBean();
      ExtendedResource resource = new ExtendedResource();
      
      mmbean.setManagedResource(resource, "ObjectReference");
      mmbean.setModelMBeanInfo(resource.getMBeanInfo());
      
      server.registerMBean(mmbean, oname);
      
      MyInterface2 mbean = (MyInterface2)MBeanProxy.get(
            MyInterface2.class, oname, server);
            
      assertTrue(mbean.doOperation().equals("doOperation"));
      
      try
      {
         assertTrue(mbean.executeThis("executeThis").equals("executeThis"));
      }
      catch (ClassCastException e) {
         fail("KNOWN ISSUE: proxy generates incorrect JMX invocation " +
              "signature in case argument subclasses are used.");
      }
   }
   
   public void testInheritanceInProxyAttribute() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      ObjectName oname   = new ObjectName("test:test=test");
      
      XMBean mmbean = new XMBean();
      ExtendedResource resource = new ExtendedResource();
      
      mmbean.setManagedResource(resource, "ObjectReference");
      mmbean.setModelMBeanInfo(resource.getMBeanInfo());
      
      server.registerMBean(mmbean, oname);
      
      MyInterface2 mbean = (MyInterface2)MBeanProxy.get(
            MyInterface2.class, oname, server);
            
      mbean.setAttribute3("Attribute3");
      
      assertTrue(mbean.getAttribute3().equals("Attribute3"));
   }
   
   public void testInheritanceInProxyReturnType() throws Exception
   {
      MBeanServer server = MBeanServerFactory.createMBeanServer();
      ObjectName oname   = new ObjectName("test:test=test");
      
      XMBean mmbean = new XMBean();
      ExtendedResource resource = new ExtendedResource();
      
      mmbean.setManagedResource(resource, "ObjectReference");
      mmbean.setModelMBeanInfo(resource.getMBeanInfo());
      
      server.registerMBean(mmbean, oname);
      
      MyInterface2 mbean = (MyInterface2)MBeanProxy.get(
            MyInterface2.class, oname, server);
            
      assertTrue(mbean.runMe("runMe").equals("runMe"));
   }
   
}
