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 com.fasterxml.jackson.core.JsonFactory; 020import com.fasterxml.jackson.core.JsonGenerationException; 021import com.fasterxml.jackson.core.JsonGenerator; 022import java.io.Closeable; 023import java.io.IOException; 024import java.io.OutputStreamWriter; 025import java.io.PrintWriter; 026import java.lang.management.ManagementFactory; 027import java.lang.reflect.Array; 028import java.nio.charset.StandardCharsets; 029import java.util.Iterator; 030import java.util.Set; 031import javax.management.AttributeNotFoundException; 032import javax.management.InstanceNotFoundException; 033import javax.management.IntrospectionException; 034import javax.management.MBeanAttributeInfo; 035import javax.management.MBeanException; 036import javax.management.MBeanInfo; 037import javax.management.MBeanServer; 038import javax.management.MalformedObjectNameException; 039import javax.management.ObjectName; 040import javax.management.ReflectionException; 041import javax.management.RuntimeErrorException; 042import javax.management.RuntimeMBeanException; 043import javax.management.openmbean.CompositeData; 044import javax.management.openmbean.CompositeType; 045import javax.management.openmbean.TabularData; 046import org.apache.yetus.audience.InterfaceAudience; 047import org.slf4j.Logger; 048import org.slf4j.LoggerFactory; 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 final JsonFactory jsonFactory; 057 058 public JSONBean() { 059 this.jsonFactory = new JsonFactory(); 060 } 061 062 /** 063 * Use dumping out mbeans as JSON. 064 */ 065 public interface Writer extends Closeable { 066 void write(final String key, final String value) throws JsonGenerationException, IOException; 067 int write(final MBeanServer mBeanServer, ObjectName qry, String attribute, 068 final boolean description) throws IOException; 069 void flush() throws IOException; 070 } 071 072 public Writer open(final PrintWriter writer) throws IOException { 073 final JsonGenerator jg = jsonFactory.createJsonGenerator(writer); 074 jg.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET); 075 jg.useDefaultPrettyPrinter(); 076 jg.writeStartObject(); 077 return new Writer() { 078 @Override 079 public void flush() throws IOException { 080 jg.flush(); 081 } 082 083 @Override 084 public void close() throws IOException { 085 jg.close(); 086 } 087 088 @Override 089 public void write(String key, String value) throws JsonGenerationException, IOException { 090 jg.writeStringField(key, value); 091 } 092 093 @Override 094 public int write(MBeanServer mBeanServer, ObjectName qry, String attribute, 095 boolean description) 096 throws IOException { 097 return JSONBean.write(jg, mBeanServer, qry, attribute, description); 098 } 099 }; 100 } 101 102 /** 103 * @return Return non-zero if failed to find bean. 0 104 */ 105 private static int write(final JsonGenerator jg, 106 final MBeanServer mBeanServer, ObjectName qry, String attribute, 107 final boolean description) 108 throws IOException { 109 LOG.trace("Listing beans for "+qry); 110 Set<ObjectName> names = null; 111 names = mBeanServer.queryNames(qry, null); 112 jg.writeArrayFieldStart("beans"); 113 Iterator<ObjectName> it = names.iterator(); 114 while (it.hasNext()) { 115 ObjectName oname = it.next(); 116 MBeanInfo minfo; 117 String code = ""; 118 String descriptionStr = null; 119 Object attributeinfo = null; 120 try { 121 minfo = mBeanServer.getMBeanInfo(oname); 122 code = minfo.getClassName(); 123 if (description) descriptionStr = minfo.getDescription(); 124 String prs = ""; 125 try { 126 if ("org.apache.commons.modeler.BaseModelMBean".equals(code)) { 127 prs = "modelerType"; 128 code = (String) mBeanServer.getAttribute(oname, prs); 129 } 130 if (attribute != null) { 131 prs = attribute; 132 attributeinfo = mBeanServer.getAttribute(oname, prs); 133 } 134 } catch (RuntimeMBeanException e) { 135 // UnsupportedOperationExceptions happen in the normal course of business, 136 // so no need to log them as errors all the time. 137 if (e.getCause() instanceof UnsupportedOperationException) { 138 if (LOG.isTraceEnabled()) { 139 LOG.trace("Getting attribute " + prs + " of " + oname + " threw " + e); 140 } 141 } else { 142 LOG.error("Getting attribute " + prs + " of " + oname + " threw an exception", e); 143 } 144 return 0; 145 } catch (AttributeNotFoundException e) { 146 // If the modelerType attribute was not found, the class name is used 147 // instead. 148 LOG.error("getting attribute " + prs + " of " + oname 149 + " threw an exception", e); 150 } catch (MBeanException e) { 151 // The code inside the attribute getter threw an exception so log it, 152 // and fall back on the class name 153 LOG.error("getting attribute " + prs + " of " + oname 154 + " threw an exception", e); 155 } catch (RuntimeException e) { 156 // For some reason even with an MBeanException available to them 157 // Runtime exceptionscan still find their way through, so treat them 158 // the same as MBeanException 159 LOG.error("getting attribute " + prs + " of " + oname 160 + " threw an exception", e); 161 } catch (ReflectionException e) { 162 // This happens when the code inside the JMX bean (setter?? from the 163 // java docs) threw an exception, so log it and fall back on the 164 // class name 165 LOG.error("getting attribute " + prs + " of " + oname 166 + " threw an exception", e); 167 } 168 } catch (InstanceNotFoundException e) { 169 //Ignored for some reason the bean was not found so don't output it 170 continue; 171 } catch (IntrospectionException e) { 172 // This is an internal error, something odd happened with reflection so 173 // log it and don't output the bean. 174 LOG.error("Problem while trying to process JMX query: " + qry 175 + " with MBean " + oname, e); 176 continue; 177 } catch (ReflectionException e) { 178 // This happens when the code inside the JMX bean threw an exception, so 179 // log it and don't output the bean. 180 LOG.error("Problem while trying to process JMX query: " + qry 181 + " with MBean " + oname, e); 182 continue; 183 } 184 185 jg.writeStartObject(); 186 jg.writeStringField("name", oname.toString()); 187 if (description && descriptionStr != null && descriptionStr.length() > 0) { 188 jg.writeStringField("description", descriptionStr); 189 } 190 jg.writeStringField("modelerType", code); 191 if (attribute != null && attributeinfo == null) { 192 jg.writeStringField("result", "ERROR"); 193 jg.writeStringField("message", "No attribute with name " + attribute + " was found."); 194 jg.writeEndObject(); 195 jg.writeEndArray(); 196 jg.close(); 197 return -1; 198 } 199 200 if (attribute != null) { 201 writeAttribute(jg, attribute, descriptionStr, attributeinfo); 202 } else { 203 MBeanAttributeInfo[] attrs = minfo.getAttributes(); 204 for (int i = 0; i < attrs.length; i++) { 205 writeAttribute(jg, mBeanServer, oname, description, attrs[i]); 206 } 207 } 208 jg.writeEndObject(); 209 } 210 jg.writeEndArray(); 211 return 0; 212 } 213 214 private static void writeAttribute(final JsonGenerator jg, 215 final MBeanServer mBeanServer, ObjectName oname, 216 final boolean description, final MBeanAttributeInfo attr) 217 throws IOException { 218 if (!attr.isReadable()) { 219 return; 220 } 221 String attName = attr.getName(); 222 if ("modelerType".equals(attName)) { 223 return; 224 } 225 if (attName.indexOf("=") >= 0 || attName.indexOf(":") >= 0 || attName.indexOf(" ") >= 0) { 226 return; 227 } 228 String descriptionStr = description? attr.getDescription(): null; 229 Object value = null; 230 try { 231 value = mBeanServer.getAttribute(oname, attName); 232 } catch (RuntimeMBeanException e) { 233 // UnsupportedOperationExceptions happen in the normal course of business, 234 // so no need to log them as errors all the time. 235 if (e.getCause() instanceof UnsupportedOperationException) { 236 if (LOG.isTraceEnabled()) { 237 LOG.trace("Getting attribute " + attName + " of " + oname + " threw " + e); 238 } 239 } else { 240 LOG.error("getting attribute "+attName+" of "+oname+" threw an exception", e); 241 } 242 return; 243 } catch (RuntimeErrorException e) { 244 // RuntimeErrorException happens when an unexpected failure occurs in getAttribute 245 // for example https://issues.apache.org/jira/browse/DAEMON-120 246 LOG.debug("getting attribute "+attName+" of "+oname+" threw an exception", e); 247 return; 248 } catch (AttributeNotFoundException e) { 249 //Ignored the attribute was not found, which should never happen because the bean 250 //just told us that it has this attribute, but if this happens just don't output 251 //the attribute. 252 return; 253 } catch (MBeanException e) { 254 //The code inside the attribute getter threw an exception so log it, and 255 // skip outputting the attribute 256 LOG.error("getting attribute "+attName+" of "+oname+" threw an exception", e); 257 return; 258 } catch (RuntimeException e) { 259 //For some reason even with an MBeanException available to them Runtime exceptions 260 //can still find their way through, so treat them the same as MBeanException 261 LOG.error("getting attribute "+attName+" of "+oname+" threw an exception", e); 262 return; 263 } catch (ReflectionException e) { 264 //This happens when the code inside the JMX bean (setter?? from the java docs) 265 //threw an exception, so log it and skip outputting the attribute 266 LOG.error("getting attribute "+attName+" of "+oname+" threw an exception", e); 267 return; 268 } catch (InstanceNotFoundException e) { 269 //Ignored the mbean itself was not found, which should never happen because we 270 //just accessed it (perhaps something unregistered in-between) but if this 271 //happens just don't output the attribute. 272 return; 273 } 274 275 writeAttribute(jg, attName, descriptionStr, value); 276 } 277 278 private static void writeAttribute(JsonGenerator jg, String attName, final String descriptionStr, 279 Object value) 280 throws IOException { 281 boolean description = false; 282 if (descriptionStr != null && descriptionStr.length() > 0 && !attName.equals(descriptionStr)) { 283 description = true; 284 jg.writeFieldName(attName); 285 jg.writeStartObject(); 286 jg.writeFieldName("description"); 287 jg.writeString(descriptionStr); 288 jg.writeFieldName("value"); 289 writeObject(jg, description, value); 290 jg.writeEndObject(); 291 } else { 292 jg.writeFieldName(attName); 293 writeObject(jg, description, value); 294 } 295 } 296 297 private static void writeObject(final JsonGenerator jg, final boolean description, Object value) 298 throws IOException { 299 if(value == null) { 300 jg.writeNull(); 301 } else { 302 Class<?> c = value.getClass(); 303 if (c.isArray()) { 304 jg.writeStartArray(); 305 int len = Array.getLength(value); 306 for (int j = 0; j < len; j++) { 307 Object item = Array.get(value, j); 308 writeObject(jg, description, item); 309 } 310 jg.writeEndArray(); 311 } else if(value instanceof Number) { 312 Number n = (Number)value; 313 if (Double.isFinite(n.doubleValue())) { 314 jg.writeNumber(n.toString()); 315 } else { 316 jg.writeString(n.toString()); 317 } 318 } else if(value instanceof Boolean) { 319 Boolean b = (Boolean)value; 320 jg.writeBoolean(b); 321 } else if(value instanceof CompositeData) { 322 CompositeData cds = (CompositeData)value; 323 CompositeType comp = cds.getCompositeType(); 324 Set<String> keys = comp.keySet(); 325 jg.writeStartObject(); 326 for (String key: keys) { 327 writeAttribute(jg, key, null, cds.get(key)); 328 } 329 jg.writeEndObject(); 330 } else if(value instanceof TabularData) { 331 TabularData tds = (TabularData)value; 332 jg.writeStartArray(); 333 for(Object entry : tds.values()) { 334 writeObject(jg, description, entry); 335 } 336 jg.writeEndArray(); 337 } else { 338 jg.writeString(value.toString()); 339 } 340 } 341 } 342 343 /** 344 * Dump out all registered mbeans as json on System.out. 345 * @throws IOException 346 * @throws MalformedObjectNameException 347 */ 348 public static void dumpAllBeans() throws IOException, MalformedObjectNameException { 349 try (PrintWriter writer = new PrintWriter( 350 new OutputStreamWriter(System.out, StandardCharsets.UTF_8))) { 351 JSONBean dumper = new JSONBean(); 352 try (JSONBean.Writer jsonBeanWriter = dumper.open(writer)) { 353 MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); 354 jsonBeanWriter.write(mbeanServer, new ObjectName("*:*"), null, false); 355 } 356 } 357 } 358}