1 /*
   2  * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug 6713777
  27  * @summary Test that exception messages include all relevant information
  28  * @author Eamonn McManus
  29  */
  30 
  31 import javax.management.ConstructorParameters;
  32 import java.io.File;
  33 import java.lang.reflect.InvocationTargetException;
  34 import java.lang.reflect.Method;
  35 import java.lang.reflect.Type;
  36 import java.util.ArrayList;
  37 import java.util.HashMap;
  38 import java.util.List;
  39 import javax.management.JMX;
  40 import javax.management.MBeanServer;
  41 import javax.management.MBeanServerFactory;
  42 import javax.management.NotCompliantMBeanException;
  43 import javax.management.ObjectName;
  44 
  45 public class ExceptionDiagnosisTest {
  46     private static volatile String failure;
  47 
  48     // ------ Illegal MXBeans ------
  49 
  50     // Test that all of BdelloidMXBean, Rotifer, and File appear in the
  51     // exception messages.  File is not an allowed type because of recursive
  52     // getters like "File getParentFile()".
  53     public static interface BdelloidMXBean {
  54         public Rotifer getRotifer();
  55     }
  56 
  57     public static class Bdelloid implements BdelloidMXBean {
  58         public Rotifer getRotifer() {
  59             return null;
  60         }
  61     }
  62 
  63     public static class Rotifer {
  64         public File getFile() {
  65             return null;
  66         }
  67     }
  68 
  69     // Test that all of IndirectHashMapMXBean, HashMapContainer, and
  70     // HashMap<String,String> appear in the exception messages.
  71     // HashMap<String,String> is not an allowed type because only the
  72     // java.util interface such as Map are allowed with generic parameters,
  73     // not their concrete implementations like HashMap.
  74     public static interface IndirectHashMapMXBean {
  75         public HashMapContainer getContainer();
  76     }
  77 
  78     public static class IndirectHashMap implements IndirectHashMapMXBean {
  79         public HashMapContainer getContainer() {
  80             return null;
  81         }
  82     }
  83 
  84     public static class HashMapContainer {
  85         public HashMap<String, String> getHashMap() {return null;}
  86     }
  87 
  88     // ------ MXBeans that are legal but where proxies are not ------
  89 
  90     // Test that all of BlimMXBean, BlimContainer, Blim, and Blam appear
  91     // in the exception messages for a proxy for this MXBean.  Blam is
  92     // legal in MXBeans but is not reconstructible so you cannot make
  93     // a proxy for BlimMXBean.
  94     public static interface BlimMXBean {
  95         public BlimContainer getBlimContainer();
  96     }
  97 
  98     public static class BlimImpl implements BlimMXBean {
  99         public BlimContainer getBlimContainer() {
 100             return null;
 101         }
 102     }
 103 
 104     public static class BlimContainer {
 105         public Blim getBlim() {return null;}
 106         public void setBlim(Blim blim) {}
 107     }
 108 
 109     public static class Blim {
 110         public Blam getBlam() {return null;}
 111         public void setBlam(Blam blam) {}
 112     }
 113 
 114     public static class Blam {
 115         public Blam(int x) {}
 116 
 117         public int getX() {return 0;}
 118     }
 119 
 120 
 121     // ------ Property name differing only in case ------
 122 
 123     public static interface CaseProbMXBean {
 124         public CaseProb getCaseProb();
 125     }
 126 
 127     public static class CaseProbImpl implements CaseProbMXBean {
 128         public CaseProb getCaseProb() {return null;}
 129     }
 130 
 131     public static class CaseProb {
 132         @ConstructorParameters({"urlPath"})
 133         public CaseProb(String urlPath) {}
 134 
 135         public String getURLPath() {return null;}
 136     }
 137 
 138 
 139     public static void main(String[] args) throws Exception {
 140         testMXBeans(new Bdelloid(), BdelloidMXBean.class, Rotifer.class, File.class);
 141         testMXBeans(new IndirectHashMap(),
 142                 IndirectHashMapMXBean.class, HashMapContainer.class,
 143                 HashMapContainer.class.getMethod("getHashMap").getGenericReturnType());
 144 
 145         testProxies(new BlimImpl(), BlimMXBean.class, BlimMXBean.class,
 146                 BlimContainer.class, Blim.class, Blam.class);
 147 
 148         testCaseProb();
 149 
 150         if (failure == null)
 151             System.out.println("TEST PASSED");
 152         else
 153             throw new Exception("TEST FAILED: " + failure);
 154     }
 155 
 156     private static void testMXBeans(Object mbean, Type... expectedTypes)
 157             throws Exception {
 158         try {
 159             MBeanServer mbs = MBeanServerFactory.newMBeanServer();
 160             ObjectName name = new ObjectName("a:b=c");
 161             mbs.registerMBean(mbean, name);
 162             fail("No exception from registerMBean for " + mbean);
 163         } catch (NotCompliantMBeanException e) {
 164             checkExceptionChain("MBean " + mbean, e, expectedTypes);
 165         }
 166     }
 167 
 168     private static <T> void testProxies(
 169             Object mbean, Class<T> mxbeanClass, Type... expectedTypes)
 170             throws Exception {
 171         MBeanServer mbs = MBeanServerFactory.newMBeanServer();
 172         ObjectName name = new ObjectName("a:b=c");
 173         mbs.registerMBean(mbean, name);
 174         T proxy = JMX.newMXBeanProxy(mbs, name, mxbeanClass);
 175         List<Method> methods = new ArrayList<Method>();
 176         for (Method m : mxbeanClass.getMethods()) {
 177             if (m.getDeclaringClass() == mxbeanClass)
 178                 methods.add(m);
 179         }
 180         if (methods.size() != 1) {
 181             fail("TEST BUG: expected to find exactly one method in " +
 182                     mxbeanClass.getName() + ": " + methods);
 183         }
 184         Method getter = methods.get(0);
 185         try {
 186             try {
 187                 getter.invoke(proxy);
 188                 fail("No exception from proxy method " + getter.getName() +
 189                         " in " + mxbeanClass.getName());
 190             } catch (InvocationTargetException e) {
 191                 Throwable cause = e.getCause();
 192                 if (cause instanceof Exception)
 193                     throw (Exception) cause;
 194                 else
 195                     throw (Error) cause;
 196             }
 197         } catch (IllegalArgumentException e) {
 198             checkExceptionChain(
 199                     "Proxy for " + mxbeanClass.getName(), e, expectedTypes);
 200         }
 201     }
 202 
 203     private static void testCaseProb() throws Exception {
 204         MBeanServer mbs = MBeanServerFactory.newMBeanServer();
 205         ObjectName name = new ObjectName("a:b=c");
 206         mbs.registerMBean(new CaseProbImpl(), name);
 207         CaseProbMXBean proxy = JMX.newMXBeanProxy(mbs, name, CaseProbMXBean.class);
 208         try {
 209             CaseProb prob = proxy.getCaseProb();
 210             fail("No exception from proxy method getCaseProb");
 211         } catch (IllegalArgumentException e) {
 212             String messageChain = messageChain(e);
 213             if (messageChain.contains("URLPath")) {
 214                 System.out.println("Message chain contains URLPath as required: "
 215                         + messageChain);
 216             } else {
 217                 fail("Exception chain for CaseProb does not mention property" +
 218                         " URLPath differing only in case");
 219                 System.out.println("Full stack trace:");
 220                 e.printStackTrace(System.out);
 221             }
 222         }
 223     }
 224 
 225     private static void checkExceptionChain(
 226             String what, Throwable e, Type[] expectedTypes) {
 227         System.out.println("Exceptions in chain for " + what + ":");
 228         for (Throwable t = e; t != null; t = t.getCause())
 229             System.out.println(".." + t);
 230 
 231         String messageChain = messageChain(e);
 232 
 233         // Now check that each of the classes is mentioned in those messages
 234         for (Type type : expectedTypes) {
 235             String name = (type instanceof Class) ?
 236                 ((Class<?>) type).getName() : type.toString();
 237             if (!messageChain.contains(name)) {
 238                 fail("Exception chain for " + what + " does not mention " +
 239                         name);
 240                 System.out.println("Full stack trace:");
 241                 e.printStackTrace(System.out);
 242             }
 243         }
 244 
 245         System.out.println();
 246     }
 247 
 248     private static String messageChain(Throwable t) {
 249         String msg = "//";
 250         for ( ; t != null; t = t.getCause())
 251             msg += " " + t.getMessage() + " //";
 252         return msg;
 253     }
 254 
 255     private static void fail(String why) {
 256         failure = why;
 257         System.out.println("FAIL: " + why);
 258     }
 259 }