001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.hadoop.hbase.util; 018 019import java.io.Closeable; 020import java.io.IOException; 021import java.io.OutputStreamWriter; 022import java.io.PrintWriter; 023import java.lang.management.ManagementFactory; 024import java.lang.reflect.Array; 025import java.nio.charset.StandardCharsets; 026import java.util.Iterator; 027import java.util.Set; 028import javax.management.AttributeNotFoundException; 029import javax.management.InstanceNotFoundException; 030import javax.management.IntrospectionException; 031import javax.management.MBeanAttributeInfo; 032import javax.management.MBeanException; 033import javax.management.MBeanInfo; 034import javax.management.MBeanServer; 035import javax.management.MalformedObjectNameException; 036import javax.management.ObjectName; 037import javax.management.ReflectionException; 038import javax.management.RuntimeErrorException; 039import javax.management.RuntimeMBeanException; 040import javax.management.openmbean.CompositeData; 041import javax.management.openmbean.CompositeType; 042import javax.management.openmbean.TabularData; 043import org.apache.yetus.audience.InterfaceAudience; 044import org.slf4j.Logger; 045import org.slf4j.LoggerFactory; 046 047import org.apache.hbase.thirdparty.com.google.gson.Gson; 048import org.apache.hbase.thirdparty.com.google.gson.stream.JsonWriter; 049 050/** 051 * Utility for doing JSON and MBeans. 052 */ 053@InterfaceAudience.Private 054public class JSONBean { 055 private static final Logger LOG = LoggerFactory.getLogger(JSONBean.class); 056 private static final Gson GSON = GsonUtil.createGson().create(); 057 058 /** 059 * Use dumping out mbeans as JSON. 060 */ 061 public interface Writer extends Closeable { 062 063 void write(String key, String value) throws IOException; 064 065 int write(MBeanServer mBeanServer, ObjectName qry, String attribute, boolean description) 066 throws IOException; 067 068 void flush() throws IOException; 069 } 070 071 /** 072 * Notice that, closing the return {@link Writer} will not close the {@code writer} passed in, you 073 * still need to close the {@code writer} by yourself. 074 * <p/> 075 * This is because that, we can only finish the json after you call {@link Writer#close()}. So if 076 * we just close the {@code writer}, you can write nothing after finished the json. 077 */ 078 public Writer open(final PrintWriter writer) throws IOException { 079 JsonWriter jsonWriter = GSON.newJsonWriter(new java.io.Writer() { 080 081 @Override 082 public void write(char[] cbuf, int off, int len) throws IOException { 083 writer.write(cbuf, off, len); 084 } 085 086 @Override 087 public void flush() throws IOException { 088 writer.flush(); 089 } 090 091 @Override 092 public void close() throws IOException { 093 // do nothing 094 } 095 }); 096 jsonWriter.setIndent(" "); 097 jsonWriter.beginObject(); 098 return new Writer() { 099 @Override 100 public void flush() throws IOException { 101 jsonWriter.flush(); 102 } 103 104 @Override 105 public void close() throws IOException { 106 jsonWriter.endObject(); 107 jsonWriter.close(); 108 } 109 110 @Override 111 public void write(String key, String value) throws IOException { 112 jsonWriter.name(key).value(value); 113 } 114 115 @Override 116 public int write(MBeanServer mBeanServer, ObjectName qry, String attribute, 117 boolean description) throws IOException { 118 return JSONBean.write(jsonWriter, mBeanServer, qry, attribute, description); 119 } 120 }; 121 } 122 123 /** 124 * @return Return non-zero if failed to find bean. 0 125 */ 126 private static int write(JsonWriter writer, MBeanServer mBeanServer, ObjectName qry, 127 String attribute, boolean description) throws IOException { 128 LOG.trace("Listing beans for " + qry); 129 Set<ObjectName> names = null; 130 names = mBeanServer.queryNames(qry, null); 131 writer.name("beans").beginArray(); 132 Iterator<ObjectName> it = names.iterator(); 133 while (it.hasNext()) { 134 ObjectName oname = it.next(); 135 MBeanInfo minfo; 136 String code = ""; 137 String descriptionStr = null; 138 Object attributeinfo = null; 139 try { 140 minfo = mBeanServer.getMBeanInfo(oname); 141 code = minfo.getClassName(); 142 if (description) { 143 descriptionStr = minfo.getDescription(); 144 } 145 String prs = ""; 146 try { 147 if ("org.apache.commons.modeler.BaseModelMBean".equals(code)) { 148 prs = "modelerType"; 149 code = (String) mBeanServer.getAttribute(oname, prs); 150 } 151 if (attribute != null) { 152 prs = attribute; 153 attributeinfo = mBeanServer.getAttribute(oname, prs); 154 } 155 } catch (RuntimeMBeanException e) { 156 // UnsupportedOperationExceptions happen in the normal course of business, 157 // so no need to log them as errors all the time. 158 if (e.getCause() instanceof UnsupportedOperationException) { 159 if (LOG.isTraceEnabled()) { 160 LOG.trace("Getting attribute " + prs + " of " + oname + " threw " + e); 161 } 162 } else { 163 LOG.error("Getting attribute " + prs + " of " + oname + " threw an exception", e); 164 } 165 return 0; 166 } catch (AttributeNotFoundException e) { 167 // If the modelerType attribute was not found, the class name is used 168 // instead. 169 LOG.error("getting attribute " + prs + " of " + oname + " threw an exception", e); 170 } catch (MBeanException e) { 171 // The code inside the attribute getter threw an exception so log it, 172 // and fall back on the class name 173 LOG.error("getting attribute " + prs + " of " + oname + " threw an exception", e); 174 } catch (RuntimeException e) { 175 // For some reason even with an MBeanException available to them 176 // Runtime exceptionscan still find their way through, so treat them 177 // the same as MBeanException 178 LOG.error("getting attribute " + prs + " of " + oname + " threw an exception", e); 179 } catch (ReflectionException e) { 180 // This happens when the code inside the JMX bean (setter?? from the 181 // java docs) threw an exception, so log it and fall back on the 182 // class name 183 LOG.error("getting attribute " + prs + " of " + oname + " threw an exception", e); 184 } 185 } catch (InstanceNotFoundException e) { 186 // Ignored for some reason the bean was not found so don't output it 187 continue; 188 } catch (IntrospectionException e) { 189 // This is an internal error, something odd happened with reflection so 190 // log it and don't output the bean. 191 LOG.error("Problem while trying to process JMX query: " + qry + " with MBean " + oname, e); 192 continue; 193 } catch (ReflectionException e) { 194 // This happens when the code inside the JMX bean threw an exception, so 195 // log it and don't output the bean. 196 LOG.error("Problem while trying to process JMX query: " + qry + " with MBean " + oname, e); 197 continue; 198 } 199 writer.beginObject(); 200 writer.name("name").value(oname.toString()); 201 if (description && descriptionStr != null && descriptionStr.length() > 0) { 202 writer.name("description").value(descriptionStr); 203 } 204 writer.name("modelerType").value(code); 205 if (attribute != null && attributeinfo == null) { 206 writer.name("result").value("ERROR"); 207 writer.name("message").value("No attribute with name " + attribute + " was found."); 208 writer.endObject(); 209 writer.endArray(); 210 writer.close(); 211 return -1; 212 } 213 214 if (attribute != null) { 215 writeAttribute(writer, attribute, descriptionStr, attributeinfo); 216 } else { 217 MBeanAttributeInfo[] attrs = minfo.getAttributes(); 218 for (int i = 0; i < attrs.length; i++) { 219 writeAttribute(writer, mBeanServer, oname, description, attrs[i]); 220 } 221 } 222 writer.endObject(); 223 } 224 writer.endArray(); 225 return 0; 226 } 227 228 private static void writeAttribute(JsonWriter writer, MBeanServer mBeanServer, ObjectName oname, 229 boolean description, MBeanAttributeInfo attr) throws IOException { 230 if (!attr.isReadable()) { 231 return; 232 } 233 String attName = attr.getName(); 234 if ("modelerType".equals(attName)) { 235 return; 236 } 237 if (attName.indexOf("=") >= 0 || attName.indexOf(":") >= 0 || attName.indexOf(" ") >= 0) { 238 return; 239 } 240 String descriptionStr = description ? attr.getDescription() : null; 241 Object value = null; 242 try { 243 value = mBeanServer.getAttribute(oname, attName); 244 } catch (RuntimeMBeanException e) { 245 // UnsupportedOperationExceptions happen in the normal course of business, 246 // so no need to log them as errors all the time. 247 if (e.getCause() instanceof UnsupportedOperationException) { 248 if (LOG.isTraceEnabled()) { 249 LOG.trace("Getting attribute " + attName + " of " + oname + " threw " + e); 250 } 251 } else { 252 LOG.error("getting attribute " + attName + " of " + oname + " threw an exception", e); 253 } 254 return; 255 } catch (RuntimeErrorException e) { 256 // RuntimeErrorException happens when an unexpected failure occurs in getAttribute 257 // for example https://issues.apache.org/jira/browse/DAEMON-120 258 LOG.debug("getting attribute " + attName + " of " + oname + " threw an exception", e); 259 return; 260 } catch (AttributeNotFoundException e) { 261 // Ignored the attribute was not found, which should never happen because the bean 262 // just told us that it has this attribute, but if this happens just don't output 263 // the attribute. 264 return; 265 } catch (MBeanException e) { 266 // The code inside the attribute getter threw an exception so log it, and 267 // skip outputting the attribute 268 LOG.error("getting attribute " + attName + " of " + oname + " threw an exception", e); 269 return; 270 } catch (RuntimeException e) { 271 // For some reason even with an MBeanException available to them Runtime exceptions 272 // can still find their way through, so treat them the same as MBeanException 273 LOG.error("getting attribute " + attName + " of " + oname + " threw an exception", e); 274 return; 275 } catch (ReflectionException e) { 276 // This happens when the code inside the JMX bean (setter?? from the java docs) 277 // threw an exception, so log it and skip outputting the attribute 278 LOG.error("getting attribute " + attName + " of " + oname + " threw an exception", e); 279 return; 280 } catch (InstanceNotFoundException e) { 281 // Ignored the mbean itself was not found, which should never happen because we 282 // just accessed it (perhaps something unregistered in-between) but if this 283 // happens just don't output the attribute. 284 return; 285 } 286 287 writeAttribute(writer, attName, descriptionStr, value); 288 } 289 290 private static void writeAttribute(JsonWriter writer, String attName, String descriptionStr, 291 Object value) throws IOException { 292 if (descriptionStr != null && descriptionStr.length() > 0 && !attName.equals(descriptionStr)) { 293 writer.name(attName); 294 writer.beginObject(); 295 writer.name("description").value(descriptionStr); 296 writer.name("value"); 297 writeObject(writer, value); 298 writer.endObject(); 299 } else { 300 writer.name(attName); 301 writeObject(writer, value); 302 } 303 } 304 305 private static void writeObject(JsonWriter writer, Object value) throws IOException { 306 if (value == null) { 307 writer.nullValue(); 308 } else { 309 Class<?> c = value.getClass(); 310 if (c.isArray()) { 311 writer.beginArray(); 312 int len = Array.getLength(value); 313 for (int j = 0; j < len; j++) { 314 Object item = Array.get(value, j); 315 writeObject(writer, item); 316 } 317 writer.endArray(); 318 } else if (value instanceof Number) { 319 Number n = (Number) value; 320 if (Double.isFinite(n.doubleValue())) { 321 writer.value(n); 322 } else { 323 writer.value(n.toString()); 324 } 325 } else if (value instanceof Boolean) { 326 Boolean b = (Boolean) value; 327 writer.value(b); 328 } else if (value instanceof CompositeData) { 329 CompositeData cds = (CompositeData) value; 330 CompositeType comp = cds.getCompositeType(); 331 Set<String> keys = comp.keySet(); 332 writer.beginObject(); 333 for (String key : keys) { 334 writeAttribute(writer, key, null, cds.get(key)); 335 } 336 writer.endObject(); 337 } else if (value instanceof TabularData) { 338 TabularData tds = (TabularData) value; 339 writer.beginArray(); 340 for (Object entry : tds.values()) { 341 writeObject(writer, entry); 342 } 343 writer.endArray(); 344 } else { 345 writer.value(value.toString()); 346 } 347 } 348 } 349 350 /** 351 * Dump out all registered mbeans as json on System.out. 352 */ 353 public static void dumpAllBeans() throws IOException, MalformedObjectNameException { 354 try (PrintWriter writer = 355 new PrintWriter(new OutputStreamWriter(System.out, StandardCharsets.UTF_8))) { 356 JSONBean dumper = new JSONBean(); 357 try (JSONBean.Writer jsonBeanWriter = dumper.open(writer)) { 358 MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); 359 jsonBeanWriter.write(mbeanServer, new ObjectName("*:*"), null, false); 360 } 361 } 362 } 363}