1 /* 2 * Copyright (c) 2005, 2008, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.management.openmbean; 27 28 import com.sun.jmx.mbeanserver.MXBeanLookup; 29 import com.sun.jmx.mbeanserver.MXBeanMapping; 30 import com.sun.jmx.mbeanserver.MXBeanMappingFactory; 31 import com.sun.jmx.mbeanserver.DefaultMXBeanMappingFactory; 32 import java.lang.reflect.InvocationHandler; 33 import java.lang.reflect.Method; 34 import java.lang.reflect.Proxy; 35 36 /** 37 <p>An {@link InvocationHandler} that forwards getter methods to a 38 {@link CompositeData}. If you have an interface that contains 39 only getter methods (such as {@code String getName()} or 40 {@code boolean isActive()}) then you can use this class in 41 conjunction with the {@link Proxy} class to produce an implementation 42 of the interface where each getter returns the value of the 43 corresponding item in a {@code CompositeData}.</p> 44 45 <p>For example, suppose you have an interface like this: 46 47 <blockquote> 48 <pre> 49 public interface NamedNumber { 50 public int getNumber(); 51 public String getName(); 52 } 53 </pre> 54 </blockquote> 55 56 and a {@code CompositeData} constructed like this: 57 58 <blockquote> 59 <pre> 60 CompositeData cd = 61 new {@link CompositeDataSupport}( 62 someCompositeType, 63 new String[] {"number", "name"}, 64 new Object[] {<b>5</b>, "five"} 65 ); 66 </pre> 67 </blockquote> 68 69 then you can construct an object implementing {@code NamedNumber} 70 and backed by the object {@code cd} like this: 71 72 <blockquote> 73 <pre> 74 InvocationHandler handler = 75 new CompositeDataInvocationHandler(cd); 76 NamedNumber nn = (NamedNumber) 77 Proxy.newProxyInstance(NamedNumber.class.getClassLoader(), 78 new Class[] {NamedNumber.class}, 79 handler); 80 </pre> 81 </blockquote> 82 83 A call to {@code nn.getNumber()} will then return <b>5</b>.</p> 84 85 <p>If the first letter of the property defined by a getter is a 86 capital, then this handler will look first for an item in the 87 {@code CompositeData} beginning with a capital, then, if that is 88 not found, for an item beginning with the corresponding lowercase 89 letter or code point. For a getter called {@code getNumber()}, the 90 handler will first look for an item called {@code Number}, then for 91 {@code number}. If the getter is called {@code getnumber()}, then 92 the item must be called {@code number}.</p> 93 94 <p>If the method given to {@link #invoke invoke} is the method 95 {@code boolean equals(Object)} inherited from {@code Object}, then 96 it will return true if and only if the argument is a {@code Proxy} 97 whose {@code InvocationHandler} is also a {@code 98 CompositeDataInvocationHandler} and whose backing {@code 99 CompositeData} is equal (not necessarily identical) to this 100 object's. If the method given to {@code invoke} is the method 101 {@code int hashCode()} inherited from {@code Object}, then it will 102 return a value that is consistent with this definition of {@code 103 equals}: if two objects are equal according to {@code equals}, then 104 they will have the same {@code hashCode}.</p> 105 106 @since 1.6 107 */ 108 public class CompositeDataInvocationHandler implements InvocationHandler { 109 /** 110 <p>Construct a handler backed by the given {@code 111 CompositeData}.</p> 112 113 @param compositeData the {@code CompositeData} that will supply 114 information to getters. 115 116 @throws IllegalArgumentException if {@code compositeData} 117 is null. 118 */ 119 public CompositeDataInvocationHandler(CompositeData compositeData) { 120 this(compositeData, null); 121 } 122 123 /** 124 <p>Construct a handler backed by the given {@code 125 CompositeData}.</p> 126 127 @param mbsc the {@code MBeanServerConnection} related to this 128 {@code CompositeData}. This is only relevant if a method in 129 the interface for which this is an invocation handler returns 130 a type that is an MXBean interface. Otherwise, it can be null. 131 132 @param compositeData the {@code CompositeData} that will supply 133 information to getters. 134 135 @throws IllegalArgumentException if {@code compositeData} 136 is null. 137 */ 138 CompositeDataInvocationHandler(CompositeData compositeData, 139 MXBeanLookup lookup) { 140 if (compositeData == null) 141 throw new IllegalArgumentException("compositeData"); 142 this.compositeData = compositeData; 143 this.lookup = lookup; 144 } 145 146 /** 147 Return the {@code CompositeData} that was supplied to the 148 constructor. 149 @return the {@code CompositeData} that this handler is backed 150 by. This is never null. 151 */ 152 public CompositeData getCompositeData() { 153 assert compositeData != null; 154 return compositeData; 155 } 156 157 public Object invoke(Object proxy, Method method, Object[] args) 158 throws Throwable { 159 final String methodName = method.getName(); 160 161 // Handle the methods from java.lang.Object 162 if (method.getDeclaringClass() == Object.class) { 163 if (methodName.equals("toString") && args == null) 164 return "Proxy[" + compositeData + "]"; 165 else if (methodName.equals("hashCode") && args == null) 166 return compositeData.hashCode() + 0x43444948; 167 else if (methodName.equals("equals") && args.length == 1 168 && method.getParameterTypes()[0] == Object.class) 169 return equals(proxy, args[0]); 170 else { 171 /* Either someone is calling invoke by hand, or 172 it is a non-final method from Object overriden 173 by the generated Proxy. At the time of writing, 174 the only non-final methods in Object that are not 175 handled above are finalize and clone, and these 176 are not overridden in generated proxies. */ 177 return method.invoke(this, args); 178 } 179 } 180 181 String propertyName = DefaultMXBeanMappingFactory.propertyName(method); 182 if (propertyName == null) { 183 throw new IllegalArgumentException("Method is not getter: " + 184 method.getName()); 185 } 186 Object openValue; 187 if (compositeData.containsKey(propertyName)) 188 openValue = compositeData.get(propertyName); 189 else { 190 String decap = DefaultMXBeanMappingFactory.decapitalize(propertyName); 191 if (compositeData.containsKey(decap)) 192 openValue = compositeData.get(decap); 193 else { 194 final String msg = 195 "No CompositeData item " + propertyName + 196 (decap.equals(propertyName) ? "" : " or " + decap) + 197 " to match " + methodName; 198 throw new IllegalArgumentException(msg); 199 } 200 } 201 MXBeanMapping mapping = 202 MXBeanMappingFactory.DEFAULT.mappingForType(method.getGenericReturnType(), 203 MXBeanMappingFactory.DEFAULT); 204 return mapping.fromOpenValue(openValue); 205 } 206 207 /* This method is called when equals(Object) is 208 * called on our proxy and hence forwarded to us. For example, if we 209 * are a proxy for an interface like this: 210 * public interface GetString { 211 * public String string(); 212 * } 213 * then we must compare equal to another CompositeDataInvocationHandler 214 * proxy for the same interface and where string() returns the same value. 215 * 216 * You might think that we should also compare equal to another 217 * object that implements GetString directly rather than using 218 * Proxy, provided that its string() returns the same result as 219 * ours, and in fact an earlier version of this class did that (by 220 * converting the other object into a CompositeData and comparing 221 * that with ours). But in fact that doesn't make a great deal of 222 * sense because there's absolutely no guarantee that the 223 * resulting equals would be reflexive (otherObject.equals(this) 224 * might be false even if this.equals(otherObject) is true), and, 225 * especially, there's no way we could generate a hashCode() that 226 * would be equal to otherObject.hashCode() when 227 * this.equals(otherObject), because we don't know how 228 * otherObject.hashCode() is computed. 229 */ 230 private boolean equals(Object proxy, Object other) { 231 if (other == null) 232 return false; 233 234 final Class<?> proxyClass = proxy.getClass(); 235 final Class<?> otherClass = other.getClass(); 236 if (proxyClass != otherClass) 237 return false; 238 InvocationHandler otherih = Proxy.getInvocationHandler(other); 239 if (!(otherih instanceof CompositeDataInvocationHandler)) 240 return false; 241 CompositeDataInvocationHandler othercdih = 242 (CompositeDataInvocationHandler) otherih; 243 return compositeData.equals(othercdih.compositeData); 244 } 245 246 private final CompositeData compositeData; 247 private final MXBeanLookup lookup; 248 }