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: */
017:
018: package org.apache.catalina.manager.host;
019:
020: import java.io.File;
021: import java.io.FileOutputStream;
022: import java.io.IOException;
023: import java.io.InputStream;
024: import java.io.OutputStream;
025: import java.io.PrintWriter;
026: import java.util.StringTokenizer;
027:
028: import javax.management.MBeanServer;
029: import javax.servlet.ServletException;
030: import javax.servlet.UnavailableException;
031: import javax.servlet.http.HttpServlet;
032: import javax.servlet.http.HttpServletRequest;
033: import javax.servlet.http.HttpServletResponse;
034:
035: import org.apache.catalina.Container;
036: import org.apache.catalina.ContainerServlet;
037: import org.apache.catalina.Context;
038: import org.apache.catalina.Engine;
039: import org.apache.catalina.Globals;
040: import org.apache.catalina.Host;
041: import org.apache.catalina.Lifecycle;
042: import org.apache.catalina.Wrapper;
043: import org.apache.catalina.core.StandardHost;
044: import org.apache.catalina.startup.HostConfig;
045: import org.apache.catalina.util.StringManager;
046: import org.apache.tomcat.util.modeler.Registry;
047: import org.apache.catalina.core.ContainerBase;
048:
049: /**
050: * Servlet that enables remote management of the virtual hosts installed
051: * on the server. Normally, this functionality will be protected by
052: * a security constraint in the web application deployment descriptor.
053: * However, this requirement can be relaxed during testing.
054: * <p>
055: * This servlet examines the value returned by <code>getPathInfo()</code>
056: * and related query parameters to determine what action is being requested.
057: * The following actions and parameters (starting after the servlet path)
058: * are supported:
059: * <ul>
060: * <li><b>/add?name={host-name}&aliases={host-aliases}&manager={manager}</b> -
061: * Create and add a new virtual host. The <code>host-name</code> attribute
062: * indicates the name of the new host. The <code>host-aliases</code>
063: * attribute is a comma separated list of the host alias names.
064: * The <code>manager</code> attribute is a boolean value indicating if the
065: * webapp manager will be installed in the newly created host (optional,
066: * false by default).</li>
067: * <li><b>/remove?name={host-name}</b> - Remove a virtual host.
068: * The <code>host-name</code> attribute indicates the name of the host.
069: * </li>
070: * <li><b>/list</b> - List the virtual hosts installed on the server.
071: * Each host will be listed with the following format
072: * <code>host-name#host-aliases</code>.</li>
073: * <li><b>/start?name={host-name}</b> - Start the virtual host.</li>
074: * <li><b>/stop?name={host-name}</b> - Stop the virtual host.</li>
075: * </ul>
076: * <p>
077: * <b>NOTE</b> - Attempting to stop or remove the host containing
078: * this servlet itself will not succeed. Therefore, this servlet should
079: * generally be deployed in a separate virtual host.
080: * <p>
081: * <b>NOTE</b> - For security reasons, this application will not operate
082: * when accessed via the invoker servlet. You must explicitly map this servlet
083: * with a servlet mapping, and you will always want to protect it with
084: * appropriate security constraints as well.
085: * <p>
086: * The following servlet initialization parameters are recognized:
087: * <ul>
088: * <li><b>debug</b> - The debugging detail level that controls the amount
089: * of information that is logged by this servlet. Default is zero.
090: * </ul>
091: *
092: * @author Craig R. McClanahan
093: * @author Remy Maucherat
094: * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (mar., 24 oct. 2006) $
095: */
096:
097: public class HostManagerServlet extends HttpServlet implements
098: ContainerServlet {
099:
100: // ----------------------------------------------------- Instance Variables
101:
102: /**
103: * Path where context descriptors should be deployed.
104: */
105: protected File configBase = null;
106:
107: /**
108: * The Context container associated with our web application.
109: */
110: protected Context context = null;
111:
112: /**
113: * The debugging detail level for this servlet.
114: */
115: protected int debug = 1;
116:
117: /**
118: * The associated host.
119: */
120: protected Host host = null;
121:
122: /**
123: * The associated engine.
124: */
125: protected Engine engine = null;
126:
127: /**
128: * MBean server.
129: */
130: protected MBeanServer mBeanServer = null;
131:
132: /**
133: * The string manager for this package.
134: */
135: protected static StringManager sm = StringManager
136: .getManager(Constants.Package);
137:
138: /**
139: * The Wrapper container associated with this servlet.
140: */
141: protected Wrapper wrapper = null;
142:
143: // ----------------------------------------------- ContainerServlet Methods
144:
145: /**
146: * Return the Wrapper with which we are associated.
147: */
148: public Wrapper getWrapper() {
149:
150: return (this .wrapper);
151:
152: }
153:
154: /**
155: * Set the Wrapper with which we are associated.
156: *
157: * @param wrapper The new wrapper
158: */
159: public void setWrapper(Wrapper wrapper) {
160:
161: this .wrapper = wrapper;
162: if (wrapper == null) {
163: context = null;
164: host = null;
165: engine = null;
166: } else {
167: context = (Context) wrapper.getParent();
168: host = (Host) context.getParent();
169: engine = (Engine) host.getParent();
170: }
171:
172: // Retrieve the MBean server
173: mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
174:
175: }
176:
177: // --------------------------------------------------------- Public Methods
178:
179: /**
180: * Finalize this servlet.
181: */
182: public void destroy() {
183:
184: ; // No actions necessary
185:
186: }
187:
188: /**
189: * Process a GET request for the specified resource.
190: *
191: * @param request The servlet request we are processing
192: * @param response The servlet response we are creating
193: *
194: * @exception IOException if an input/output error occurs
195: * @exception ServletException if a servlet-specified error occurs
196: */
197: public void doGet(HttpServletRequest request,
198: HttpServletResponse response) throws IOException,
199: ServletException {
200:
201: // Verify that we were not accessed using the invoker servlet
202: if (request.getAttribute(Globals.INVOKED_ATTR) != null)
203: throw new UnavailableException(sm
204: .getString("hostManagerServlet.cannotInvoke"));
205:
206: // Identify the request parameters that we need
207: String command = request.getPathInfo();
208: if (command == null)
209: command = request.getServletPath();
210: String name = request.getParameter("name");
211:
212: // Prepare our output writer to generate the response message
213: response.setContentType("text/plain; charset="
214: + Constants.CHARSET);
215: PrintWriter writer = response.getWriter();
216:
217: // Process the requested command
218: if (command == null) {
219: writer
220: .println(sm
221: .getString("hostManagerServlet.noCommand"));
222: } else if (command.equals("/add")) {
223: add(request, writer, name, false);
224: } else if (command.equals("/remove")) {
225: remove(writer, name);
226: } else if (command.equals("/list")) {
227: list(writer);
228: } else if (command.equals("/start")) {
229: start(writer, name);
230: } else if (command.equals("/stop")) {
231: stop(writer, name);
232: } else {
233: writer.println(sm.getString(
234: "hostManagerServlet.unknownCommand", command));
235: }
236:
237: // Finish up the response
238: writer.flush();
239: writer.close();
240:
241: }
242:
243: /**
244: * Add host with the given parameters.
245: *
246: * @param request The request
247: * @param writer The output writer
248: * @param name The host name
249: * @param htmlMode Flag value
250: */
251: protected void add(HttpServletRequest request, PrintWriter writer,
252: String name, boolean htmlMode) {
253: String aliases = request.getParameter("aliases");
254: String appBase = request.getParameter("appBase");
255: boolean manager = booleanParameter(request, "manager", false,
256: htmlMode);
257: boolean autoDeploy = booleanParameter(request, "autoDeploy",
258: true, htmlMode);
259: boolean deployOnStartup = booleanParameter(request,
260: "deployOnStartup", true, htmlMode);
261: boolean deployXML = booleanParameter(request, "deployXML",
262: true, htmlMode);
263: boolean unpackWARs = booleanParameter(request, "unpackWARs",
264: true, htmlMode);
265: boolean xmlNamespaceAware = booleanParameter(request,
266: "xmlNamespaceAware", false, htmlMode);
267: boolean xmlValidation = booleanParameter(request,
268: "xmlValidation", false, htmlMode);
269: add(writer, name, aliases, appBase, manager, autoDeploy,
270: deployOnStartup, deployXML, unpackWARs,
271: xmlNamespaceAware, xmlValidation);
272: }
273:
274: /**
275: * extract boolean value from checkbox with default
276: * @param request
277: * @param parameter
278: * @param theDefault
279: * @param htmlMode
280: * @return
281: */
282: protected boolean booleanParameter(HttpServletRequest request,
283: String parameter, boolean theDefault, boolean htmlMode) {
284: String value = request.getParameter(parameter);
285: boolean booleanValue = theDefault;
286: if (value != null) {
287: if (htmlMode) {
288: if (value.equals("on")) {
289: booleanValue = true;
290: }
291: } else if (theDefault) {
292: if (value.equals("false")) {
293: booleanValue = false;
294: }
295: } else if (value.equals("true")) {
296: booleanValue = true;
297: }
298: } else if (htmlMode)
299: booleanValue = false;
300: return booleanValue;
301: }
302:
303: /**
304: * Initialize this servlet.
305: */
306: public void init() throws ServletException {
307:
308: // Ensure that our ContainerServlet properties have been set
309: if ((wrapper == null) || (context == null))
310: throw new UnavailableException(sm
311: .getString("hostManagerServlet.noWrapper"));
312:
313: // Verify that we were not accessed using the invoker servlet
314: String servletName = getServletConfig().getServletName();
315: if (servletName == null)
316: servletName = "";
317: if (servletName.startsWith("org.apache.catalina.INVOKER."))
318: throw new UnavailableException(sm
319: .getString("hostManagerServlet.cannotInvoke"));
320:
321: // Set our properties from the initialization parameters
322: String value = null;
323: try {
324: value = getServletConfig().getInitParameter("debug");
325: debug = Integer.parseInt(value);
326: } catch (Throwable t) {
327: ;
328: }
329:
330: }
331:
332: // -------------------------------------------------------- Private Methods
333:
334: /**
335: * Add a host using the specified parameters.
336: *
337: * @param writer Writer to render results to
338: * @param name host name
339: * @param aliases comma separated alias list
340: * @param appBase application base for the host
341: * @param manager should the manager webapp be deployed to the new host ?
342: */
343: protected synchronized void add(PrintWriter writer, String name,
344: String aliases, String appBase, boolean manager,
345: boolean autoDeploy, boolean deployOnStartup,
346: boolean deployXML, boolean unpackWARs,
347: boolean xmlNamespaceAware, boolean xmlValidation) {
348: if (debug >= 1) {
349: log("add: Adding host '" + name + "'");
350: }
351:
352: // Validate the requested host name
353: if ((name == null) || name.length() == 0) {
354: writer.println(sm.getString(
355: "hostManagerServlet.invalidHostName", name));
356: return;
357: }
358:
359: // Check if host already exists
360: if (engine.findChild(name) != null) {
361: writer.println(sm.getString(
362: "hostManagerServlet.alreadyHost", name));
363: return;
364: }
365:
366: // Validate and create appBase
367: File appBaseFile = null;
368: if (appBase == null || appBase.length() == 0) {
369: appBase = name;
370: }
371: File file = new File(appBase);
372: if (!file.isAbsolute())
373: file = new File(System.getProperty("catalina.base"),
374: appBase);
375: try {
376: appBaseFile = file.getCanonicalFile();
377: } catch (IOException e) {
378: appBaseFile = file;
379: }
380: if (!appBaseFile.exists()) {
381: appBaseFile.mkdirs();
382: }
383:
384: // Create base for config files
385: File configBaseFile = getConfigBase(name);
386:
387: // Copy manager.xml if requested
388: if (manager) {
389: InputStream is = null;
390: OutputStream os = null;
391: try {
392: is = getServletContext().getResourceAsStream(
393: "/manager.xml");
394: os = new FileOutputStream(new File(configBaseFile,
395: "manager.xml"));
396: byte buffer[] = new byte[512];
397: int len = buffer.length;
398: while (true) {
399: len = is.read(buffer);
400: if (len == -1)
401: break;
402: os.write(buffer, 0, len);
403: }
404: } catch (IOException e) {
405: writer.println(sm
406: .getString("hostManagerServlet.managerXml"));
407: return;
408: } finally {
409: if (is != null) {
410: try {
411: is.close();
412: } catch (IOException e) {
413: }
414: }
415: if (os != null) {
416: try {
417: os.close();
418: } catch (IOException e) {
419: }
420: }
421: }
422: }
423:
424: StandardHost host = new StandardHost();
425: host.setAppBase(appBase);
426: host.setName(name);
427:
428: host.addLifecycleListener(new HostConfig());
429:
430: // Add host aliases
431: if ((aliases != null) && !("".equals(aliases))) {
432: StringTokenizer tok = new StringTokenizer(aliases, ", ");
433: while (tok.hasMoreTokens()) {
434: host.addAlias(tok.nextToken());
435: }
436: }
437: host.setAutoDeploy(autoDeploy);
438: host.setDeployOnStartup(deployOnStartup);
439: host.setDeployXML(deployXML);
440: host.setUnpackWARs(unpackWARs);
441: host.setXmlNamespaceAware(xmlNamespaceAware);
442: host.setXmlValidation(xmlValidation);
443:
444: // Add new host
445: try {
446: engine.addChild(host);
447: } catch (Exception e) {
448: writer.println(sm.getString("hostManagerServlet.exception",
449: e.toString()));
450: return;
451: }
452:
453: host = (StandardHost) engine.findChild(name);
454: if (host != null) {
455: writer
456: .println(sm.getString("hostManagerServlet.add",
457: name));
458: } else {
459: // Something failed
460: writer.println(sm.getString("hostManagerServlet.addFailed",
461: name));
462: }
463:
464: }
465:
466: /**
467: * Remove the specified host.
468: *
469: * @param writer Writer to render results to
470: * @param name host name
471: */
472: protected synchronized void remove(PrintWriter writer, String name) {
473:
474: if (debug >= 1) {
475: log("remove: Removing host '" + name + "'");
476: }
477:
478: // Validate the requested host name
479: if ((name == null) || name.length() == 0) {
480: writer.println(sm.getString(
481: "hostManagerServlet.invalidHostName", name));
482: return;
483: }
484:
485: // Check if host exists
486: if (engine.findChild(name) == null) {
487: writer.println(sm.getString("hostManagerServlet.noHost",
488: name));
489: return;
490: }
491:
492: // Prevent removing our own host
493: if (engine.findChild(name) == host) {
494: writer.println(sm.getString(
495: "hostManagerServlet.cannotRemoveOwnHost", name));
496: return;
497: }
498:
499: // Remove host
500: // Note that the host will not get physically removed
501: try {
502: Container child = engine.findChild(name);
503: engine.removeChild(child);
504: if (child instanceof ContainerBase)
505: ((ContainerBase) child).destroy();
506: } catch (Exception e) {
507: writer.println(sm.getString("hostManagerServlet.exception",
508: e.toString()));
509: return;
510: }
511:
512: Host host = (StandardHost) engine.findChild(name);
513: if (host == null) {
514: writer.println(sm.getString("hostManagerServlet.remove",
515: name));
516: } else {
517: // Something failed
518: writer.println(sm.getString(
519: "hostManagerServlet.removeFailed", name));
520: }
521:
522: }
523:
524: /**
525: * Render a list of the currently active Contexts in our virtual host.
526: *
527: * @param writer Writer to render to
528: */
529: protected void list(PrintWriter writer) {
530:
531: if (debug >= 1)
532: log("list: Listing hosts for engine '" + engine.getName()
533: + "'");
534:
535: writer.println(sm.getString("hostManagerServlet.listed", engine
536: .getName()));
537: Container[] hosts = engine.findChildren();
538: for (int i = 0; i < hosts.length; i++) {
539: Host host = (Host) hosts[i];
540: String name = host.getName();
541: String[] aliases = host.findAliases();
542: StringBuffer buf = new StringBuffer();
543: if (aliases.length > 0) {
544: buf.append(aliases[0]);
545: for (int j = 1; j < aliases.length; j++) {
546: buf.append(',').append(aliases[j]);
547: }
548: }
549: writer.println(sm.getString("hostManagerServlet.listitem",
550: name, buf.toString()));
551: }
552: }
553:
554: /**
555: * Start the host with the specified name.
556: *
557: * @param writer Writer to render to
558: * @param name Host name
559: */
560: protected void start(PrintWriter writer, String name) {
561:
562: if (debug >= 1)
563: log("start: Starting host with name '" + name + "'");
564:
565: // Validate the requested host name
566: if ((name == null) || name.length() == 0) {
567: writer.println(sm.getString(
568: "hostManagerServlet.invalidHostName", name));
569: return;
570: }
571:
572: // Check if host exists
573: if (engine.findChild(name) == null) {
574: writer.println(sm.getString("hostManagerServlet.noHost",
575: name));
576: return;
577: }
578:
579: // Prevent starting our own host
580: if (engine.findChild(name) == host) {
581: writer.println(sm.getString(
582: "hostManagerServlet.cannotStartOwnHost", name));
583: return;
584: }
585:
586: // Start host
587: try {
588: ((Lifecycle) engine.findChild(name)).start();
589: writer.println(sm.getString("hostManagerServlet.started",
590: name));
591: } catch (Throwable t) {
592: getServletContext().log(
593: sm
594: .getString(
595: "hostManagerServlet.startFailed",
596: name), t);
597: writer.println(sm.getString(
598: "hostManagerServlet.startFailed", name));
599: writer.println(sm.getString("hostManagerServlet.exception",
600: t.toString()));
601: return;
602: }
603:
604: }
605:
606: /**
607: * Start the host with the specified name.
608: *
609: * @param writer Writer to render to
610: * @param name Host name
611: */
612: protected void stop(PrintWriter writer, String name) {
613:
614: if (debug >= 1)
615: log("stop: Stopping host with name '" + name + "'");
616:
617: // Validate the requested host name
618: if ((name == null) || name.length() == 0) {
619: writer.println(sm.getString(
620: "hostManagerServlet.invalidHostName", name));
621: return;
622: }
623:
624: // Check if host exists
625: if (engine.findChild(name) == null) {
626: writer.println(sm.getString("hostManagerServlet.noHost",
627: name));
628: return;
629: }
630:
631: // Prevent starting our own host
632: if (engine.findChild(name) == host) {
633: writer.println(sm.getString(
634: "hostManagerServlet.cannotStopOwnHost", name));
635: return;
636: }
637:
638: // Start host
639: try {
640: ((Lifecycle) engine.findChild(name)).stop();
641: writer.println(sm.getString("hostManagerServlet.stopped",
642: name));
643: } catch (Throwable t) {
644: getServletContext()
645: .log(
646: sm.getString(
647: "hostManagerServlet.stopFailed",
648: name), t);
649: writer.println(sm.getString(
650: "hostManagerServlet.stopFailed", name));
651: writer.println(sm.getString("hostManagerServlet.exception",
652: t.toString()));
653: return;
654: }
655:
656: }
657:
658: // -------------------------------------------------------- Support Methods
659:
660: /**
661: * Get config base.
662: */
663: protected File getConfigBase(String hostName) {
664: File configBase = new File(System.getProperty("catalina.base"),
665: "conf");
666: if (!configBase.exists()) {
667: return null;
668: }
669: if (engine != null) {
670: configBase = new File(configBase, engine.getName());
671: }
672: if (host != null) {
673: configBase = new File(configBase, hostName);
674: }
675: configBase.mkdirs();
676: return configBase;
677: }
678:
679: }
|