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