001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.hadoop.conf;
020
021import org.apache.commons.logging.*;
022
023import org.apache.commons.lang.StringEscapeUtils;
024
025import java.util.Collection;
026import java.util.Map;
027import java.util.Enumeration;
028import java.io.IOException;
029import java.io.PrintWriter;
030
031import javax.servlet.ServletContext;
032import javax.servlet.ServletException;
033import javax.servlet.http.HttpServlet;
034import javax.servlet.http.HttpServletRequest;
035import javax.servlet.http.HttpServletResponse;
036
037import org.apache.hadoop.util.StringUtils;
038
039/**
040 * A servlet for changing a node's configuration.
041 *
042 * Reloads the configuration file, verifies whether changes are
043 * possible and asks the admin to approve the change.
044 *
045 */
046public class ReconfigurationServlet extends HttpServlet {
047  
048  private static final long serialVersionUID = 1L;
049
050  private static final Log LOG =
051    LogFactory.getLog(ReconfigurationServlet.class);
052
053  // the prefix used to fing the attribute holding the reconfigurable 
054  // for a given request
055  //
056  // we get the attribute prefix + servlet path
057  public static final String CONF_SERVLET_RECONFIGURABLE_PREFIX =
058    "conf.servlet.reconfigurable.";
059  
060  /**
061   * {@inheritDoc}
062   */
063  @Override
064  public void init() throws ServletException {
065    super.init();
066  }
067
068  private Reconfigurable getReconfigurable(HttpServletRequest req) {
069    LOG.info("servlet path: " + req.getServletPath());
070    LOG.info("getting attribute: " + CONF_SERVLET_RECONFIGURABLE_PREFIX +
071             req.getServletPath());
072    return (Reconfigurable)
073      this.getServletContext().getAttribute(CONF_SERVLET_RECONFIGURABLE_PREFIX +
074                                            req.getServletPath());
075  }
076
077  private void printHeader(PrintWriter out, String nodeName) {
078    out.print("<html><head>");
079    out.printf("<title>%s Reconfiguration Utility</title>\n", 
080               StringEscapeUtils.escapeHtml(nodeName));
081    out.print("</head><body>\n");
082    out.printf("<h1>%s Reconfiguration Utility</h1>\n",
083               StringEscapeUtils.escapeHtml(nodeName));
084  }
085
086  private void printFooter(PrintWriter out) {
087    out.print("</body></html>\n");
088  }
089  
090  /**
091   * Print configuration options that can be changed.
092   */
093  private void printConf(PrintWriter out, Reconfigurable reconf) {
094    Configuration oldConf = reconf.getConf();
095    Configuration newConf = new Configuration();
096
097    Collection<ReconfigurationUtil.PropertyChange> changes = 
098      ReconfigurationUtil.getChangedProperties(newConf, 
099                                               oldConf);
100
101    boolean changeOK = true;
102    
103    out.println("<form action=\"\" method=\"post\">");
104    out.println("<table border=\"1\">");
105    out.println("<tr><th>Property</th><th>Old value</th>");
106    out.println("<th>New value </th><th></th></tr>");
107    for (ReconfigurationUtil.PropertyChange c: changes) {
108      out.print("<tr><td>");
109      if (!reconf.isPropertyReconfigurable(c.prop)) {
110        out.print("<font color=\"red\">" + 
111                  StringEscapeUtils.escapeHtml(c.prop) + "</font>");
112        changeOK = false;
113      } else {
114        out.print(StringEscapeUtils.escapeHtml(c.prop));
115        out.print("<input type=\"hidden\" name=\"" +
116                  StringEscapeUtils.escapeHtml(c.prop) + "\" value=\"" +
117                  StringEscapeUtils.escapeHtml(c.newVal) + "\"/>");
118      }
119      out.print("</td><td>" +
120                (c.oldVal == null ? "<it>default</it>" : 
121                 StringEscapeUtils.escapeHtml(c.oldVal)) +
122                "</td><td>" +
123                (c.newVal == null ? "<it>default</it>" : 
124                 StringEscapeUtils.escapeHtml(c.newVal)) +
125                "</td>");
126      out.print("</tr>\n");
127    }
128    out.println("</table>");
129    if (!changeOK) {
130      out.println("<p><font color=\"red\">WARNING: properties marked red" +
131                  " will not be changed until the next restart.</font></p>");
132    }
133    out.println("<input type=\"submit\" value=\"Apply\" />");
134    out.println("</form>");
135  }
136
137  @SuppressWarnings("unchecked")
138  private Enumeration<String> getParams(HttpServletRequest req) {
139    return (Enumeration<String>) req.getParameterNames();
140  }
141
142  /**
143   * Apply configuratio changes after admin has approved them.
144   */
145  private void applyChanges(PrintWriter out, Reconfigurable reconf,
146                            HttpServletRequest req) 
147    throws IOException, ReconfigurationException {
148    Configuration oldConf = reconf.getConf();
149    Configuration newConf = new Configuration();
150
151    Enumeration<String> params = getParams(req);
152
153    synchronized(oldConf) {
154      while (params.hasMoreElements()) {
155        String rawParam = params.nextElement();
156        String param = StringEscapeUtils.unescapeHtml(rawParam);
157        String value =
158          StringEscapeUtils.unescapeHtml(req.getParameter(rawParam));
159        if (value != null) {
160          if (value.equals(newConf.getRaw(param)) || value.equals("default") ||
161              value.equals("null") || value.equals("")) {
162            if ((value.equals("default") || value.equals("null") || 
163                 value.equals("")) && 
164                oldConf.getRaw(param) != null) {
165              out.println("<p>Changed \"" + 
166                          StringEscapeUtils.escapeHtml(param) + "\" from \"" +
167                          StringEscapeUtils.escapeHtml(oldConf.getRaw(param)) +
168                          "\" to default</p>");
169              reconf.reconfigureProperty(param, null);
170            } else if (!value.equals("default") && !value.equals("null") &&
171                       !value.equals("") && 
172                       (oldConf.getRaw(param) == null || 
173                        !oldConf.getRaw(param).equals(value))) {
174              // change from default or value to different value
175              if (oldConf.getRaw(param) == null) {
176                out.println("<p>Changed \"" + 
177                            StringEscapeUtils.escapeHtml(param) + 
178                            "\" from default to \"" +
179                            StringEscapeUtils.escapeHtml(value) + "\"</p>");
180              } else {
181                out.println("<p>Changed \"" + 
182                            StringEscapeUtils.escapeHtml(param) + "\" from \"" +
183                            StringEscapeUtils.escapeHtml(oldConf.
184                                                         getRaw(param)) +
185                            "\" to \"" +
186                            StringEscapeUtils.escapeHtml(value) + "\"</p>");
187              }
188              reconf.reconfigureProperty(param, value);
189            } else {
190              LOG.info("property " + param + " unchanged");
191            }
192          } else {
193            // parameter value != newConf value
194            out.println("<p>\"" + StringEscapeUtils.escapeHtml(param) + 
195                        "\" not changed because value has changed from \"" +
196                        StringEscapeUtils.escapeHtml(value) + "\" to \"" +
197                        StringEscapeUtils.escapeHtml(newConf.getRaw(param)) +
198                        "\" since approval</p>");
199          }
200        }
201      }
202    }
203  }
204
205  /**
206   * {@inheritDoc}
207   */
208  @Override
209  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
210    throws ServletException, IOException {
211    LOG.info("GET");
212    PrintWriter out = resp.getWriter();
213    
214    Reconfigurable reconf = getReconfigurable(req);
215    String nodeName = reconf.getClass().getCanonicalName();
216
217    printHeader(out, nodeName);
218    printConf(out, reconf);
219    printFooter(out);
220  }
221
222  /**
223   * {@inheritDoc}
224   */
225  @Override
226  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
227    throws ServletException, IOException {
228    LOG.info("POST");
229    PrintWriter out = resp.getWriter();
230
231    Reconfigurable reconf = getReconfigurable(req);
232    String nodeName = reconf.getClass().getCanonicalName();
233
234    printHeader(out, nodeName);
235
236    try { 
237      applyChanges(out, reconf, req);
238    } catch (ReconfigurationException e) {
239      resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 
240                     StringUtils.stringifyException(e));
241      return;
242    }
243
244    out.println("<p><a href=\"" + req.getServletPath() + "\">back</a></p>");
245    printFooter(out);
246  }
247
248}