001: /*
002: * <copyright>
003: *
004: * Copyright 1997-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026:
027: package org.cougaar.planning.servlet;
028:
029: import java.io.IOException;
030: import java.io.PrintWriter;
031: import java.net.MalformedURLException;
032: import java.net.URL;
033: import java.net.URLDecoder;
034: import java.util.Enumeration;
035: import java.util.List;
036:
037: import javax.servlet.Servlet;
038: import javax.servlet.http.HttpServlet;
039: import javax.servlet.http.HttpServletRequest;
040: import javax.servlet.http.HttpServletResponse;
041:
042: import org.cougaar.core.component.ComponentDescription;
043: import org.cougaar.core.mts.MessageAddress;
044: import org.cougaar.core.node.NodeIdentificationService;
045: import org.cougaar.core.service.AgentContainmentService;
046: import org.cougaar.core.service.AgentIdentificationService;
047: import org.cougaar.core.service.MessageTransportService;
048: import org.cougaar.core.servlet.BaseServletComponent;
049: import org.cougaar.util.CSVUtility;
050:
051: /**
052: * Servlet that allows the client to add, remove, and check for
053: * the existence of Components at both the servlet's agent
054: * (eg plugin) or node (eg agent).
055: * <p>
056: * The path of the servlet is "/load". This is a bit of a
057: * misnomer, since in addition to "add" this servlet also
058: * supports "remove" and "contains".
059: * <p>
060: * The URL parameters to this servlet are:
061: * <ul><p>
062: * <li><tt>action=STRING</tt><br>
063: * Required action for container operation; this should
064: * be either "add", "remove", or "contains", where the
065: * default is "add".
066: * <p>
067: * <b>Important Note:</b>
068: * <p>
069: * "add" acts as an "ensure-is-loaded"; if the component
070: * is already loaded then the action is ignored (success).
071: * <p>
072: * "remove" similarily acts as an "ensure-is-not-loaded",
073: * where a remove of a non-loaded component is ignored
074: * (success).
075: * <p>
076: * "contains" will only return with a success status code
077: * if the component is loaded ("assert-is-loaded"). If
078: * the component is not loaded then an error response
079: * is returned.
080: * </li><p>
081: * <li><tt>target=STRING</tt><br>
082: * Optional target of where to add this component;
083: * this should be either "agent" or "node", where the
084: * default is "agent". This must agree with the
085: * "insertionPoint".</li><p>
086: * <li><tt>name=STRING</tt><br>
087: * Optional name of the component; defaults to the
088: * classname.
089: * </li><p>
090: * <li><tt>insertionPoint=STRING</tt><br>
091: * Insertion point for the component; the default is:<br>
092: * <tt>Node.AgentManager.Agent.PluginManager.Plugin</tt>.
093: * </li><p>
094: * <li><tt>classname=STRING</tt><br>
095: * Name of the component class to load.<br>
096: * If this parameter is missing then a "usage" HTML
097: * page is generated.</li><p>
098: * <li><tt>parameters=STRING1,STRING2,..,STRINGN</tt>
099: * Optional list of string parameters to pass to the
100: * component. Defaults to null.</li><p>
101: * <li><tt>codebase=STRING</tt><br>
102: * Optional codebase URL for locating the class file(s).
103: * The default is to use the node's classpath.</li><p>
104: * </ul>
105: * <p>
106: * Note the <b>SECURITY</b> issues in loading an arbitrary
107: * Component!
108: */
109: public class LoaderServletComponent extends BaseServletComponent {
110: protected MessageAddress agentId;
111: protected MessageAddress nodeId;
112:
113: protected AgentContainmentService agentContainer;
114:
115: protected NodeIdentificationService nodeIdService;
116:
117: private static final String[] VALID_ACTIONS = { "add", "remove",
118: "contains", };
119:
120: protected String getPath() {
121: return "/load";
122: }
123:
124: protected Servlet createServlet() {
125: // create inner class
126: return new MyServlet();
127: }
128:
129: // aquire services:
130: public void load() {
131: AgentIdentificationService ais = (AgentIdentificationService) serviceBroker
132: .getService(this , AgentIdentificationService.class,
133: null);
134: if (ais != null) {
135: this .agentId = ais.getMessageAddress();
136: serviceBroker.releaseService(this ,
137: AgentIdentificationService.class, ais);
138: }
139:
140: // get the nodeId
141: this .nodeIdService = (NodeIdentificationService) serviceBroker
142: .getService(this , NodeIdentificationService.class, null);
143: if (nodeIdService == null) {
144: throw new RuntimeException(
145: "Unable to obtain NodeIdentificationService for \""
146: + getPath() + "\" servlet");
147: }
148: this .nodeId = nodeIdService.getMessageAddress();
149: if (nodeId == null) {
150: throw new RuntimeException(
151: "Unable to obtain node's id? for \"" + getPath()
152: + "\" servlet");
153: }
154:
155: // get the agent containment service
156: this .agentContainer = (AgentContainmentService) serviceBroker
157: .getService(this , AgentContainmentService.class, null);
158: if (agentContainer == null) {
159: throw new RuntimeException(
160: "Unable to obtain AgentContainmentService for \""
161: + getPath() + "\" servlet");
162: }
163:
164: // FIXME get node containment service
165:
166: super .load();
167: }
168:
169: // release services:
170: public void unload() {
171: super .unload();
172: if (agentContainer != null) {
173: serviceBroker.releaseService(this ,
174: AgentContainmentService.class, agentContainer);
175: agentContainer = null;
176: }
177: if (nodeIdService != null) {
178: serviceBroker.releaseService(this ,
179: NodeIdentificationService.class, nodeIdService);
180: nodeIdService = null;
181: }
182: // release agentIdService
183: }
184:
185: private boolean performAction(String action, String target,
186: ComponentDescription desc) {
187: // add security-check here
188: if (!("agent".equalsIgnoreCase(target))) {
189: throw new UnsupportedOperationException(
190: "Only \"agent\" target is supported, not \""
191: + target + "\" (see bug 1112)");
192: }
193: if ("add".equalsIgnoreCase(action)) {
194: return agentContainer.add(desc);
195: } else if ("remove".equalsIgnoreCase(action)) {
196: return agentContainer.remove(desc);
197: } else if ("contains".equalsIgnoreCase(action)) {
198: if (!(agentContainer.contains(desc))) {
199: throw new RuntimeException(target
200: + " doesn't contain the component.");
201: }
202: return true;
203: } else {
204: throw new UnsupportedOperationException(
205: "Unknown action: \"" + action + "\"");
206: }
207: }
208:
209: /**
210: * Servlet to handle requests.
211: */
212: private class MyServlet extends HttpServlet {
213:
214: public void doGet(HttpServletRequest req,
215: HttpServletResponse res) throws IOException {
216: MyWorker mw = new MyWorker(req, res);
217: mw.execute();
218: }
219:
220: private class MyWorker {
221:
222: // from the "doGet(..)":
223: private HttpServletRequest request;
224: private HttpServletResponse response;
225:
226: // from the URL-params:
227: // (see the class-level javadocs for details)
228:
229: public static final String ACTION_PARAM = "op";
230: public String action;
231:
232: public static final String TARGET_PARAM = "target";
233: public static final String OLD_TARGET_PARAM = "into";
234: public String target;
235:
236: public static final String COMPONENT_NAME_PARAM = "name";
237: public String compName;
238:
239: public static final String INSERTION_POINT_PARAM = "insertionPoint";
240: public String insertionPoint;
241:
242: public static final String CLASSNAME_PARAM = "classname";
243: public String classname;
244:
245: public static final String PARAMETERS_PARAM = "params";
246: public List parameters;
247:
248: public static final String CODEBASE_PARAM = "codebase";
249: public String codebase;
250:
251: // add "lease", "certificate", "policy"???
252:
253: // worker constructor:
254: public MyWorker(HttpServletRequest request,
255: HttpServletResponse response) {
256: this .request = request;
257: this .response = response;
258: }
259:
260: // handle a request:
261: public void execute() throws IOException {
262: parseParams();
263: writeResponse();
264: }
265:
266: private void parseParams() throws IOException {
267: // set defaults, postpone compName default
268: action = "add";
269: target = "agent";
270: insertionPoint = "Node.AgentManager.Agent.PluginManager.Plugin";
271: // get "name=value" parameters
272: for (Enumeration en = request.getParameterNames(); en
273: .hasMoreElements();) {
274: // extract (name, value)
275: String name = (String) en.nextElement();
276: if (name == null) {
277: continue;
278: }
279: String values[] = request.getParameterValues(name);
280: int nvalues = ((values != null) ? values.length : 0);
281: if (nvalues <= 0) {
282: continue;
283: }
284: String value = values[nvalues - 1];
285: if ((value == null) || (value.length() <= 0)) {
286: continue;
287: }
288: value = URLDecoder.decode(value, "UTF-8");
289:
290: // save parameters
291: if (name.equals(ACTION_PARAM)) {
292: action = value;
293: } else if (name.equals(TARGET_PARAM)
294: || name.equals(OLD_TARGET_PARAM)) {
295: target = value;
296: } else if (name.equals(COMPONENT_NAME_PARAM)) {
297: compName = value;
298: } else if (name.equals(INSERTION_POINT_PARAM)) {
299: insertionPoint = value;
300: } else if (name.equals(CLASSNAME_PARAM)) {
301: classname = value;
302: } else if (name.equals(PARAMETERS_PARAM)) {
303: // parse (s1, s2, .., sN)
304: parameters = CSVUtility.parseToList(value);
305: } else if (name.equals(CODEBASE_PARAM)) {
306: codebase = value;
307: } else {
308: }
309: }
310: // set default compName
311: if (compName == null) {
312: compName = classname;
313: }
314: }
315:
316: private void writeResponse() throws IOException {
317: if (classname != null) {
318: ComponentDescription desc = createComponentDescription();
319: boolean ret;
320: try {
321: ret = performAction(action, target, desc);
322: } catch (Exception e) {
323: writeFailure(e);
324: return;
325: }
326: writeSuccess(ret);
327: } else {
328: writeUsage();
329: }
330: }
331:
332: private void writeUsage() throws IOException {
333: response.setContentType("text/html");
334: PrintWriter out = response.getWriter();
335: out.print("<html><head><title>" + "Component loader"
336: + "</title></head>" + "<body>\n"
337: + "<h2>Component Loader Servlet</h2>\n"
338: + "Please fill in these parameters:\n");
339: writeParameters(out);
340: out.print("</body></html>\n");
341: out.close();
342: }
343:
344: private void writeFailure(Exception e) throws IOException {
345: // select response message
346: // String msg = action+" failed";
347:
348: // generate an HTML error response, with a 404 error code.
349: //
350: // use "setStatus" instead of "sendError" -- see bug 1259
351:
352: response.setContentType("text/html");
353: response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
354: PrintWriter out = response.getWriter();
355:
356: // build up response
357: out.print("<html><head><title>" + action + " failed"
358: + "</title></head>" + "<body>\n"
359: + "<center><h1>" + action + " failed"
360: + "</h1></center>" + "<p><pre>\n");
361: boolean isUnknownClass = false;
362: for (Throwable t = e; t != null; t = t.getCause()) {
363: if (t instanceof ClassNotFoundException) {
364: isUnknownClass = true;
365: break;
366: }
367: }
368: if (isUnknownClass) {
369: out.print("<h2>Unknown class \"" + classname
370: + "\"</h2>");
371: } else {
372: e.printStackTrace(out);
373: }
374: out.print("\n</pre><p>"
375: + "Please double-check these parameters:\n");
376: writeParameters(out);
377: out.print("</body></html>\n");
378: out.close();
379: }
380:
381: private void writeSuccess(boolean ret) throws IOException {
382: // select response message
383: String msg;
384: if ("add".equalsIgnoreCase(action)) {
385: if (ret) {
386: msg = "New component added";
387: } else {
388: msg = "Component already exists";
389: }
390: } else if ("remove".equalsIgnoreCase(action)) {
391: if (ret) {
392: msg = "Removed existing component";
393: } else {
394: msg = "No such component exists";
395: }
396: } else if ("contains".equalsIgnoreCase(action)) {
397: if (ret) {
398: msg = "Component exists";
399: } else {
400: // never - exception thrown when !contains
401: msg = "Internal error";
402: }
403: } else {
404: // never - exception thrown when action is unknown
405: msg = "Internal error";
406: }
407: // write response
408: response.setContentType("text/html");
409: PrintWriter out = response.getWriter();
410: out.print("<html><head><title>");
411: out.print(msg);
412: out.print("</title></head>" + "<body>\n"
413: + "<center><h1>");
414: out.print(msg);
415: out.print("</h1></center><p>\n");
416: writeParameters(out);
417: out.print("</body></html>\n");
418: out.close();
419: }
420:
421: private void writeParameters(PrintWriter out)
422: throws IOException {
423: out.print("<form method=\"GET\" action=\"");
424: out.print(request.getRequestURI());
425: out.print("\">\n" + "<table>\n" + "<tr><td>" + "Action"
426: + "</td><td>\n" + "<select name=\""
427: + ACTION_PARAM + "\">");
428: for (int i = 0; i < VALID_ACTIONS.length; i++) {
429: String ai = VALID_ACTIONS[i];
430: out.print("<option value=\"");
431: out.print(ai);
432: out.print("\"");
433: if (ai.equalsIgnoreCase(action)) {
434: out.print(" selected");
435: }
436: out.print(">");
437: out.print(ai);
438: out.print("</option>");
439: }
440: out.print("</select>\n" + "</td></tr>\n" + "<tr><td>"
441: + "Target" + "</td><td>\n" + "<select name=\""
442: + TARGET_PARAM + "\">"
443: + "<option value=\"agent\"");
444: if ("agent".equalsIgnoreCase(target)) {
445: out.print(" selected");
446: }
447: out.print(">agent ");
448: out.print(agentId);
449: out.print("</option>" + "<option value=\"node\"");
450: if ("node".equalsIgnoreCase(target)) {
451: out.print(" selected");
452: }
453: out.print(">node ");
454: out.print(nodeId);
455: out.print("</option>" + "</select>\n" + "</td></tr>\n"
456: + "<tr><td>" + "Insertion point" + "</td><td>"
457: + "<input type=\"text\" name=\""
458: + INSERTION_POINT_PARAM + "\" size=70");
459: if (insertionPoint != null) {
460: out.print(" value=\"");
461: out.print(insertionPoint);
462: out.print("\"");
463: }
464: out.print("> <i>(required)</i>" + "</td></tr>\n"
465: + "<tr><td>" + "Classname" + "</td><td>"
466: + "<input type=\"text\" name=\""
467: + CLASSNAME_PARAM + "\" size=70");
468: if (classname != null) {
469: out.print(" value=\"");
470: out.print(classname);
471: out.print("\"");
472: }
473: out.print("> <i>(required)</i>" + "</td></tr>\n"
474: + "<tr><td>" + "Parameters" + "</td><td>"
475: + "<input type=\"text\" name=\""
476: + PARAMETERS_PARAM + "\" size=70");
477: if (parameters != null) {
478: out.print(" value=\"");
479: int n = (parameters.size() - 1);
480: for (int i = 0; i <= n; i++) {
481: out.print(parameters.get(i));
482: if (i < n) {
483: out.print(", ");
484: }
485: }
486: out.print("\"");
487: }
488: out.print("> <i>(optional comma-separated list)</i>"
489: + "</td></tr>\n" + "<tr><td>"
490: + "Component name" + "</td><td>"
491: + "<input type=\"text\" name=\""
492: + COMPONENT_NAME_PARAM + "\" size=70");
493: if (compName != null) {
494: out.print(" value=\"");
495: out.print(compName);
496: out.print("\"");
497: }
498: out.print("> <i>(defaults to the classname)</i>"
499: + "</td></tr>\n" + "<tr><td>" + "Codebase URL"
500: + "</td><td>" + "<input type=\"text\" name=\""
501: + CODEBASE_PARAM + "\"");
502: if (codebase != null) {
503: out.print(" value=\"");
504: out.print(codebase);
505: out.print("\"");
506: }
507: out.print(" size=70> <i>(optional; see bug 1029)</i>"
508: + "</td></tr>\n" + "<tr><td colwidth=2>"
509: + "<input type=\"submit\" value=\"Submit\">"
510: + "</td></tr>\n" + "</table>\n" + "</form>\n");
511: }
512:
513: private ComponentDescription createComponentDescription() {
514: // convert codebase to url
515: URL codebaseURL;
516: if (codebase != null) {
517: try {
518: codebaseURL = new URL(codebase);
519: } catch (MalformedURLException badUrlE) {
520: throw new IllegalArgumentException(
521: "Illegal codebase URL: " + badUrlE);
522: }
523: } else {
524: codebaseURL = null;
525: }
526:
527: // create a new ComponentDescription
528: ComponentDescription desc = new ComponentDescription(
529: compName, insertionPoint, classname,
530: codebaseURL, parameters, null, // certificate
531: null, // lease
532: null); // policy
533: return desc;
534: }
535: }
536: }
537: }
|