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}