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 default int write(MBeanServer mBeanServer, ObjectName qry, String attribute, 070 boolean description) throws IOException { 071 return write(mBeanServer, qry, attribute, description, null); 072 } 073 074 int write(MBeanServer mBeanServer, ObjectName qry, String attribute, boolean description, 075 ObjectName excluded) throws IOException; 076 077 void flush() throws IOException; 078 } 079 080 /** 081 * Notice that, closing the return {@link Writer} will not close the {@code writer} passed in, you 082 * still need to close the {@code writer} by yourself. 083 * <p/> 084 * This is because that, we can only finish the json after you call {@link Writer#close()}. So if 085 * we just close the {@code writer}, you can write nothing after finished the json. 086 */ 087 public Writer open(final PrintWriter writer) throws IOException { 088 JsonWriter jsonWriter = GSON.newJsonWriter(new java.io.Writer() { 089 090 @Override 091 public void write(char[] cbuf, int off, int len) throws IOException { 092 writer.write(cbuf, off, len); 093 } 094 095 @Override 096 public void flush() throws IOException { 097 writer.flush(); 098 } 099 100 @Override 101 public void close() throws IOException { 102 // do nothing 103 } 104 }); 105 jsonWriter.setIndent(" "); 106 jsonWriter.beginObject(); 107 return new Writer() { 108 @Override 109 public void flush() throws IOException { 110 jsonWriter.flush(); 111 } 112 113 @Override 114 public void close() throws IOException { 115 jsonWriter.endObject(); 116 jsonWriter.close(); 117 } 118 119 @Override 120 public void write(String key, String value) throws IOException { 121 jsonWriter.name(key).value(value); 122 } 123 124 @Override 125 public int write(MBeanServer mBeanServer, ObjectName qry, String attribute, 126 boolean description, ObjectName excluded) throws IOException { 127 return JSONBean.write(jsonWriter, mBeanServer, qry, attribute, description, excluded); 128 } 129 }; 130 } 131 132 /** Returns Return non-zero if failed to find bean. 0 */ 133 private static int write(JsonWriter writer, MBeanServer mBeanServer, ObjectName qry, 134 String attribute, boolean description, ObjectName excluded) throws IOException { 135 LOG.debug("Listing beans for {}", qry); 136 Set<ObjectName> names = mBeanServer.queryNames(qry, null); 137 writer.name("beans").beginArray(); 138 Iterator<ObjectName> it = names.iterator(); 139 Pattern[] matchingPattern = null; 140 while (it.hasNext()) { 141 ObjectName oname = it.next(); 142 if (excluded != null && excluded.apply(oname)) { 143 continue; 144 } 145 MBeanInfo minfo; 146 String code = ""; 147 String descriptionStr = null; 148 Object attributeinfo = null; 149 try { 150 minfo = mBeanServer.getMBeanInfo(oname); 151 code = minfo.getClassName(); 152 if (description) { 153 descriptionStr = minfo.getDescription(); 154 } 155 String prs = ""; 156 try { 157 if ("org.apache.commons.modeler.BaseModelMBean".equals(code)) { 158 prs = "modelerType"; 159 code = (String) mBeanServer.getAttribute(oname, prs); 160 } 161 if (attribute != null) { 162 String[] patternAttr = null; 163 if (attribute.contains(ASTERICK)) { 164 if (attribute.contains(COMMA)) { 165 patternAttr = attribute.split(COMMA); 166 } else { 167 patternAttr = new String[1]; 168 patternAttr[0] = attribute; 169 } 170 matchingPattern = new Pattern[patternAttr.length]; 171 for (int i = 0; i < patternAttr.length; i++) { 172 matchingPattern[i] = Pattern.compile(patternAttr[i]); 173 } 174 // nullify the attribute 175 attribute = null; 176 } else { 177 prs = attribute; 178 attributeinfo = mBeanServer.getAttribute(oname, prs); 179 } 180 } 181 } catch (RuntimeMBeanException e) { 182 // UnsupportedOperationExceptions happen in the normal course of business, 183 // so no need to log them as errors all the time. 184 if (e.getCause() instanceof UnsupportedOperationException) { 185 if (LOG.isTraceEnabled()) { 186 LOG.trace("Getting attribute " + prs + " of " + oname + " threw " + e); 187 } 188 } else { 189 LOG.error("Getting attribute " + prs + " of " + oname + " threw an exception", e); 190 } 191 return 0; 192 } catch (AttributeNotFoundException e) { 193 // If the modelerType attribute was not found, the class name is used 194 // instead. 195 LOG.error("getting attribute " + prs + " of " + oname + " threw an exception", e); 196 } catch (MBeanException e) { 197 // The code inside the attribute getter threw an exception so log it, 198 // and fall back on the class name 199 LOG.error("getting attribute " + prs + " of " + oname + " threw an exception", e); 200 } catch (RuntimeException e) { 201 // For some reason even with an MBeanException available to them 202 // Runtime exceptionscan still find their way through, so treat them 203 // the same as MBeanException 204 LOG.error("getting attribute " + prs + " of " + oname + " threw an exception", e); 205 } catch (ReflectionException e) { 206 // This happens when the code inside the JMX bean (setter?? from the 207 // java docs) threw an exception, so log it and fall back on the 208 // class name 209 LOG.error("getting attribute " + prs + " of " + oname + " threw an exception", e); 210 } 211 } catch (InstanceNotFoundException e) { 212 // Ignored for some reason the bean was not found so don't output it 213 continue; 214 } catch (IntrospectionException e) { 215 // This is an internal error, something odd happened with reflection so 216 // log it and don't output the bean. 217 LOG.error("Problem while trying to process JMX query: " + qry + " with MBean " + oname, e); 218 continue; 219 } catch (ReflectionException e) { 220 // This happens when the code inside the JMX bean threw an exception, so 221 // log it and don't output the bean. 222 LOG.error("Problem while trying to process JMX query: " + qry + " with MBean " + oname, e); 223 continue; 224 } 225 writer.beginObject(); 226 writer.name("name").value(oname.toString()); 227 if (description && descriptionStr != null && descriptionStr.length() > 0) { 228 writer.name("description").value(descriptionStr); 229 } 230 writer.name("modelerType").value(code); 231 if (attribute != null && attributeinfo == null) { 232 writer.name("result").value("ERROR"); 233 writer.name("message").value("No attribute with name " + attribute + " was found."); 234 writer.endObject(); 235 writer.endArray(); 236 writer.close(); 237 return -1; 238 } 239 240 if (attribute != null) { 241 writeAttribute(writer, attribute, descriptionStr, attributeinfo); 242 } else { 243 MBeanAttributeInfo[] attrs = minfo.getAttributes(); 244 for (int i = 0; i < attrs.length; i++) { 245 writeAttribute(writer, mBeanServer, oname, description, matchingPattern, attrs[i]); 246 } 247 } 248 writer.endObject(); 249 } 250 writer.endArray(); 251 return 0; 252 } 253 254 private static void writeAttribute(JsonWriter writer, MBeanServer mBeanServer, ObjectName oname, 255 boolean description, Pattern pattern[], MBeanAttributeInfo attr) throws IOException { 256 if (!attr.isReadable()) { 257 return; 258 } 259 String attName = attr.getName(); 260 if ("modelerType".equals(attName)) { 261 return; 262 } 263 if (attName.indexOf("=") >= 0 || attName.indexOf(" ") >= 0) { 264 return; 265 } 266 267 if (pattern != null) { 268 boolean matchFound = false; 269 for (Pattern compile : pattern) { 270 // check if we have any match 271 if (compile.matcher(attName).find()) { 272 matchFound = true; 273 break; 274 } 275 } 276 if (!matchFound) { 277 return; 278 } 279 } 280 281 String descriptionStr = description ? attr.getDescription() : null; 282 Object value = null; 283 try { 284 value = mBeanServer.getAttribute(oname, attName); 285 } catch (RuntimeMBeanException e) { 286 // UnsupportedOperationExceptions happen in the normal course of business, 287 // so no need to log them as errors all the time. 288 if (e.getCause() instanceof UnsupportedOperationException) { 289 if (LOG.isTraceEnabled()) { 290 LOG.trace("Getting attribute " + attName + " of " + oname + " threw " + e); 291 } 292 } else { 293 LOG.error("getting attribute " + attName + " of " + oname + " threw an exception", e); 294 } 295 return; 296 } catch (RuntimeErrorException e) { 297 // RuntimeErrorException happens when an unexpected failure occurs in getAttribute 298 // for example https://issues.apache.org/jira/browse/DAEMON-120 299 LOG.debug("getting attribute " + attName + " of " + oname + " threw an exception", e); 300 return; 301 } catch (AttributeNotFoundException e) { 302 // Ignored the attribute was not found, which should never happen because the bean 303 // just told us that it has this attribute, but if this happens just don't output 304 // the attribute. 305 return; 306 } catch (MBeanException e) { 307 // The code inside the attribute getter threw an exception so log it, and 308 // skip outputting the attribute 309 LOG.error("getting attribute " + attName + " of " + oname + " threw an exception", e); 310 return; 311 } catch (RuntimeException e) { 312 // For some reason even with an MBeanException available to them Runtime exceptions 313 // can still find their way through, so treat them the same as MBeanException 314 LOG.error("getting attribute " + attName + " of " + oname + " threw an exception", e); 315 return; 316 } catch (ReflectionException e) { 317 // This happens when the code inside the JMX bean (setter?? from the java docs) 318 // threw an exception, so log it and skip outputting the attribute 319 LOG.error("getting attribute " + attName + " of " + oname + " threw an exception", e); 320 return; 321 } catch (InstanceNotFoundException e) { 322 // Ignored the mbean itself was not found, which should never happen because we 323 // just accessed it (perhaps something unregistered in-between) but if this 324 // happens just don't output the attribute. 325 return; 326 } 327 328 writeAttribute(writer, attName, descriptionStr, value); 329 } 330 331 private static void writeAttribute(JsonWriter writer, String attName, String descriptionStr, 332 Object value) throws IOException { 333 if (descriptionStr != null && descriptionStr.length() > 0 && !attName.equals(descriptionStr)) { 334 writer.name(attName); 335 writer.beginObject(); 336 writer.name("description").value(descriptionStr); 337 writer.name("value"); 338 writeObject(writer, value); 339 writer.endObject(); 340 } else { 341 writer.name(attName); 342 writeObject(writer, value); 343 } 344 } 345 346 private static void writeObject(JsonWriter writer, Object value) throws IOException { 347 if (value == null) { 348 writer.nullValue(); 349 } else { 350 Class<?> c = value.getClass(); 351 if (c.isArray()) { 352 writer.beginArray(); 353 int len = Array.getLength(value); 354 for (int j = 0; j < len; j++) { 355 Object item = Array.get(value, j); 356 writeObject(writer, item); 357 } 358 writer.endArray(); 359 } else if (value instanceof Number) { 360 Number n = (Number) value; 361 if (Double.isFinite(n.doubleValue())) { 362 writer.value(n); 363 } else { 364 writer.value(n.toString()); 365 } 366 } else if (value instanceof Boolean) { 367 Boolean b = (Boolean) value; 368 writer.value(b); 369 } else if (value instanceof CompositeData) { 370 CompositeData cds = (CompositeData) value; 371 CompositeType comp = cds.getCompositeType(); 372 Set<String> keys = comp.keySet(); 373 writer.beginObject(); 374 for (String key : keys) { 375 writeAttribute(writer, key, null, cds.get(key)); 376 } 377 writer.endObject(); 378 } else if (value instanceof TabularData) { 379 TabularData tds = (TabularData) value; 380 writer.beginArray(); 381 for (Object entry : tds.values()) { 382 writeObject(writer, entry); 383 } 384 writer.endArray(); 385 } else { 386 writer.value(value.toString()); 387 } 388 } 389 } 390 391 /** 392 * Dump out all registered mbeans as json on System.out. 393 */ 394 public static void dumpAllBeans() throws IOException, MalformedObjectNameException { 395 try (PrintWriter writer = 396 new PrintWriter(new OutputStreamWriter(System.out, StandardCharsets.UTF_8))) { 397 JSONBean dumper = new JSONBean(); 398 try (JSONBean.Writer jsonBeanWriter = dumper.open(writer)) { 399 MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); 400 jsonBeanWriter.write(mbeanServer, new ObjectName("*:*"), null, false); 401 } 402 } 403 } 404}