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