View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.hadoop.hbase.http.jmx;
19  
20  import java.io.IOException;
21  import java.io.PrintWriter;
22  import java.lang.management.ManagementFactory;
23  
24  import javax.management.MBeanServer;
25  import javax.management.MalformedObjectNameException;
26  import javax.management.ObjectName;
27  import javax.management.ReflectionException;
28  import javax.management.RuntimeErrorException;
29  import javax.management.RuntimeMBeanException;
30  import javax.management.openmbean.CompositeData;
31  import javax.management.openmbean.TabularData;
32  import javax.servlet.ServletException;
33  import javax.servlet.http.HttpServlet;
34  import javax.servlet.http.HttpServletRequest;
35  import javax.servlet.http.HttpServletResponse;
36  
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  import org.apache.hadoop.hbase.http.HttpServer;
40  import org.apache.hadoop.hbase.util.JSONBean;
41  
42  /*
43   * This servlet is based off of the JMXProxyServlet from Tomcat 7.0.14. It has
44   * been rewritten to be read only and to output in a JSON format so it is not
45   * really that close to the original.
46   */
47  /**
48   * Provides Read only web access to JMX.
49   * <p>
50   * This servlet generally will be placed under the /jmx URL for each
51   * HttpServer.  It provides read only
52   * access to JMX metrics.  The optional <code>qry</code> parameter
53   * may be used to query only a subset of the JMX Beans.  This query
54   * functionality is provided through the
55   * {@link MBeanServer#queryNames(ObjectName, javax.management.QueryExp)}
56   * method.
57   * <p>
58   * For example <code>http://.../jmx?qry=Hadoop:*</code> will return
59   * all hadoop metrics exposed through JMX.
60   * <p>
61   * The optional <code>get</code> parameter is used to query an specific
62   * attribute of a JMX bean.  The format of the URL is
63   * <code>http://.../jmx?get=MXBeanName::AttributeName<code>
64   * <p>
65   * For example
66   * <code>
67   * http://../jmx?get=Hadoop:service=NameNode,name=NameNodeInfo::ClusterId
68   * </code> will return the cluster id of the namenode mxbean.
69   * <p>
70   * If the <code>qry</code> or the <code>get</code> parameter is not formatted
71   * correctly then a 400 BAD REQUEST http response code will be returned.
72   * <p>
73   * If a resouce such as a mbean or attribute can not be found,
74   * a 404 SC_NOT_FOUND http response code will be returned.
75   * <p>
76   * The return format is JSON and in the form
77   * <p>
78   *  <code><pre>
79   *  {
80   *    "beans" : [
81   *      {
82   *        "name":"bean-name"
83   *        ...
84   *      }
85   *    ]
86   *  }
87   *  </pre></code>
88   *  <p>
89   *  The servlet attempts to convert the the JMXBeans into JSON. Each
90   *  bean's attributes will be converted to a JSON object member.
91   *
92   *  If the attribute is a boolean, a number, a string, or an array
93   *  it will be converted to the JSON equivalent.
94   *
95   *  If the value is a {@link CompositeData} then it will be converted
96   *  to a JSON object with the keys as the name of the JSON member and
97   *  the value is converted following these same rules.
98   *
99   *  If the value is a {@link TabularData} then it will be converted
100  *  to an array of the {@link CompositeData} elements that it contains.
101  *
102  *  All other objects will be converted to a string and output as such.
103  *
104  *  The bean's name and modelerType will be returned for all beans.
105  *
106  *  Optional paramater "callback" should be used to deliver JSONP response.
107  *
108  */
109 public class JMXJsonServlet extends HttpServlet {
110   private static final Log LOG = LogFactory.getLog(JMXJsonServlet.class);
111 
112   private static final long serialVersionUID = 1L;
113 
114   private static final String CALLBACK_PARAM = "callback";
115   /**
116    * If query string includes 'description', then we will emit bean and attribute descriptions to
117    * output IFF they are not null and IFF the description is not the same as the attribute name:
118    * i.e. specify an URL like so: /jmx?description=true
119    */
120   private static final String INCLUDE_DESCRIPTION = "description";
121 
122   /**
123    * MBean server.
124    */
125   protected transient MBeanServer mBeanServer;
126 
127   protected transient JSONBean jsonBeanWriter;
128 
129   /**
130    * Initialize this servlet.
131    */
132   @Override
133   public void init() throws ServletException {
134     // Retrieve the MBean server
135     mBeanServer = ManagementFactory.getPlatformMBeanServer();
136     this.jsonBeanWriter = new JSONBean();
137   }
138 
139   /**
140    * Process a GET request for the specified resource.
141    *
142    * @param request
143    *          The servlet request we are processing
144    * @param response
145    *          The servlet response we are creating
146    */
147   @Override
148   public void doGet(HttpServletRequest request, HttpServletResponse response) {
149     try {
150       if (!HttpServer.isInstrumentationAccessAllowed(getServletContext(), request, response)) {
151         return;
152       }
153       String jsonpcb = null;
154       PrintWriter writer = null;
155       JSONBean.Writer beanWriter = null;
156       try {
157         writer = response.getWriter();
158         beanWriter = this.jsonBeanWriter.open(writer);
159         // "callback" parameter implies JSONP outpout
160         jsonpcb = request.getParameter(CALLBACK_PARAM);
161         if (jsonpcb != null) {
162           response.setContentType("application/javascript; charset=utf8");
163           writer.write(jsonpcb + "(");
164         } else {
165           response.setContentType("application/json; charset=utf8");
166         }
167         // Should we output description on each attribute and bean?
168         String tmpStr = request.getParameter(INCLUDE_DESCRIPTION);
169         boolean description = tmpStr != null && tmpStr.length() > 0;
170 
171         // query per mbean attribute
172         String getmethod = request.getParameter("get");
173         if (getmethod != null) {
174           String[] splitStrings = getmethod.split("\\:\\:");
175           if (splitStrings.length != 2) {
176             beanWriter.write("result", "ERROR");
177             beanWriter.write("message", "query format is not as expected.");
178             beanWriter.flush();
179             response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
180             return;
181           }
182           if (beanWriter.write(this.mBeanServer, new ObjectName(splitStrings[0]),
183               splitStrings[1], description) != 0) {
184             beanWriter.flush();
185             response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
186           }
187           return;
188         }
189 
190         // query per mbean
191         String qry = request.getParameter("qry");
192         if (qry == null) {
193           qry = "*:*";
194         }
195         if (beanWriter.write(this.mBeanServer, new ObjectName(qry), null, description) != 0) {
196           beanWriter.flush();
197           response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
198         }
199       } finally {
200         if (beanWriter != null) beanWriter.close();
201         if (jsonpcb != null) {
202            writer.write(");");
203         }
204         if (writer != null) {
205           writer.close();
206         }
207       }
208     } catch (IOException e) {
209       LOG.error("Caught an exception while processing JMX request", e);
210       response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
211     } catch (MalformedObjectNameException e) {
212       LOG.error("Caught an exception while processing JMX request", e);
213       response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
214     }
215   }
216 }