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