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   * <p>
59   * For example <code>http://.../jmx?qry=Hadoop:*</code> will return
60   * all hadoop metrics exposed through JMX.
61   * </p>
62   * <p>
63   * The optional <code>get</code> parameter is used to query an specific
64   * attribute of a JMX bean.  The format of the URL is
65   * <code>http://.../jmx?get=MXBeanName::AttributeName</code>
66   * </p>
67   * <p>
68   * For example
69   * <code>
70   * http://../jmx?get=Hadoop:service=NameNode,name=NameNodeInfo::ClusterId
71   * </code> will return the cluster id of the namenode mxbean.
72   * </p>
73   * <p>
74   * If the <code>qry</code> or the <code>get</code> parameter is not formatted 
75   * correctly then a 400 BAD REQUEST http response code will be returned. 
76   * </p>
77   * <p>
78   * If a resouce such as a mbean or attribute can not be found, 
79   * a 404 SC_NOT_FOUND http response code will be returned. 
80   * </p>
81   * <p>
82   * The return format is JSON and in the form
83   * </p>
84   *  <pre><code>
85   *  {
86   *    "beans" : [
87   *      {
88   *        "name":"bean-name"
89   *        ...
90   *      }
91   *    ]
92   *  }
93   *  </code></pre>
94   *  <p>
95   *  The servlet attempts to convert the the JMXBeans into JSON. Each
96   *  bean's attributes will be converted to a JSON object member.
97   *
98   *  If the attribute is a boolean, a number, a string, or an array
99   *  it will be converted to the JSON equivalent.
100  *
101  *  If the value is a {@link CompositeData} then it will be converted
102  *  to a JSON object with the keys as the name of the JSON member and
103  *  the value is converted following these same rules.
104  *
105  *  If the value is a {@link TabularData} then it will be converted
106  *  to an array of the {@link CompositeData} elements that it contains.
107  *
108  *  All other objects will be converted to a string and output as such.
109  *
110  *  The bean's name and modelerType will be returned for all beans.
111  *
112  *  Optional paramater "callback" should be used to deliver JSONP response.
113  * </p>
114  *  
115  */
116 public class JMXJsonServlet extends HttpServlet {
117   private static final Log LOG = LogFactory.getLog(JMXJsonServlet.class);
118 
119   private static final long serialVersionUID = 1L;
120 
121   private static final String CALLBACK_PARAM = "callback";
122   /**
123    * If query string includes 'description', then we will emit bean and attribute descriptions to
124    * output IFF they are not null and IFF the description is not the same as the attribute name:
125    * i.e. specify an URL like so: /jmx?description=true
126    */
127   private static final String INCLUDE_DESCRIPTION = "description";
128 
129   /**
130    * MBean server.
131    */
132   protected transient MBeanServer mBeanServer;
133 
134   protected transient JSONBean jsonBeanWriter;
135 
136   /**
137    * Initialize this servlet.
138    */
139   @Override
140   public void init() throws ServletException {
141     // Retrieve the MBean server
142     mBeanServer = ManagementFactory.getPlatformMBeanServer();
143     this.jsonBeanWriter = new JSONBean();
144   }
145 
146   /**
147    * Process a GET request for the specified resource.
148    *
149    * @param request
150    *          The servlet request we are processing
151    * @param response
152    *          The servlet response we are creating
153    */
154   @Override
155   @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="XSS_REQUEST_PARAMETER_TO_SERVLET_WRITER",
156     justification="TODO: See HBASE-15122")
157   public void doGet(HttpServletRequest request, HttpServletResponse response) {
158     try {
159       if (!HttpServer.isInstrumentationAccessAllowed(getServletContext(), request, response)) {
160         return;
161       }
162       String jsonpcb = null;
163       PrintWriter writer = null;
164       JSONBean.Writer beanWriter = null;
165       try {
166         writer = response.getWriter();
167         beanWriter = this.jsonBeanWriter.open(writer);
168         // "callback" parameter implies JSONP outpout
169         jsonpcb = request.getParameter(CALLBACK_PARAM);
170         if (jsonpcb != null) {
171           response.setContentType("application/javascript; charset=utf8");
172           writer.write(jsonpcb + "(");
173         } else {
174           response.setContentType("application/json; charset=utf8");
175         }
176         // Should we output description on each attribute and bean?
177         String tmpStr = request.getParameter(INCLUDE_DESCRIPTION);
178         boolean description = tmpStr != null && tmpStr.length() > 0;
179 
180         // query per mbean attribute
181         String getmethod = request.getParameter("get");
182         if (getmethod != null) {
183           String[] splitStrings = getmethod.split("\\:\\:");
184           if (splitStrings.length != 2) {
185             beanWriter.write("result", "ERROR");
186             beanWriter.write("message", "query format is not as expected.");
187             beanWriter.flush();
188             response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
189             return;
190           }
191           if (beanWriter.write(this.mBeanServer, new ObjectName(splitStrings[0]),
192               splitStrings[1], description) != 0) {
193             beanWriter.flush();
194             response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
195           }
196           return;
197         }
198 
199         // query per mbean
200         String qry = request.getParameter("qry");
201         if (qry == null) {
202           qry = "*:*";
203         }
204         if (beanWriter.write(this.mBeanServer, new ObjectName(qry), null, description) != 0) {
205           beanWriter.flush();
206           response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
207         }
208       } finally {
209         if (beanWriter != null) beanWriter.close();
210         if (jsonpcb != null) {
211            writer.write(");");
212         }
213         if (writer != null) {
214           writer.close();
215         }
216       }
217     } catch (IOException e) {
218       LOG.error("Caught an exception while processing JMX request", e);
219       response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
220     } catch (MalformedObjectNameException e) {
221       LOG.error("Caught an exception while processing JMX request", e);
222       response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
223     }
224   }
225 }