001: /*
002: * <copyright>
003: *
004: * Copyright 2000-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.lib.web.service;
028:
029: import java.io.IOException;
030: import java.io.PrintWriter;
031: import java.net.URI;
032: import java.util.ArrayList;
033: import java.util.Collections;
034: import java.util.HashMap;
035: import java.util.Iterator;
036: import java.util.List;
037: import java.util.Map;
038: import javax.servlet.Servlet;
039: import javax.servlet.ServletException;
040: import javax.servlet.http.HttpServletRequest;
041: import javax.servlet.http.HttpServletResponse;
042:
043: import org.cougaar.core.component.Component;
044: import org.cougaar.core.component.ServiceBroker;
045: import org.cougaar.core.component.ServiceProvider;
046: import org.cougaar.core.node.NodeControlService;
047: import org.cougaar.core.node.NodeIdentificationService;
048: import org.cougaar.core.service.LoggingService;
049: import org.cougaar.core.service.wp.AddressEntry;
050: import org.cougaar.core.service.wp.WhitePagesService;
051: import org.cougaar.lib.web.arch.ServletRegistry;
052: import org.cougaar.lib.web.arch.root.GlobalRegistry;
053: import org.cougaar.lib.web.arch.root.Redirector;
054: import org.cougaar.lib.web.arch.root.RootServlet;
055: import org.cougaar.lib.web.arch.root.RootServletRegistry;
056: import org.cougaar.lib.web.engine.ServletEngineService;
057: import org.cougaar.lib.web.redirect.NamingSupport;
058: import org.cougaar.lib.web.redirect.ServletRedirector;
059: import org.cougaar.lib.web.redirect.ServletRedirectorService;
060: import org.cougaar.util.GenericStateModelAdapter;
061:
062: /**
063: * This component uses the {@link ServletEngineService} to advertise the
064: * root-level {@link RootServletService}, which is used by the
065: * agent-level {@link LeafServletServiceComponent}s to create the
066: * agent-internal {@link org.cougaar.core.service.ServletService}.
067: * <p>
068: * For example, node "N" creates the new root-level RootServletService
069: * for all agents. Agent "A" obtains the RootServletService,
070: * registers itself as "/$A/", and advertises the agent's internal
071: * ServletService.
072: *
073: * @property org.cougaar.lib.web.redirect.timeout
074: * Timeout in millseconds for "/$" remote redirect lookups, where
075: * 0 indicates no timeout. Defaults to 0.
076: *
077: * @see RootServletService we provide this service
078: * @see ServletEngineService required engine service
079: * @see ServletRedirectService optional redirector service
080: */
081: public class RootServletServiceComponent extends
082: GenericStateModelAdapter implements Component {
083: private ServiceBroker sb;
084:
085: private LoggingService log;
086: private ServletEngineService engine;
087: private ServletRedirectorService redirector;
088: private ServiceBroker rootsb;
089: private WhitePagesService wp;
090:
091: private String localNode;
092:
093: private GlobalRegistry globReg;
094:
095: private ServiceProvider rootSP;
096:
097: public void setServiceBroker(ServiceBroker sb) {
098: this .sb = sb;
099: }
100:
101: public void load() {
102: super .load();
103:
104: // obtain services
105: log = (LoggingService) sb.getService(this ,
106: LoggingService.class, null);
107:
108: // required servlet engine
109: engine = (ServletEngineService) sb.getService(this ,
110: ServletEngineService.class, null);
111: if (engine == null) {
112: throw new RuntimeException(
113: "Unable to obtain ServletEngineService");
114: }
115:
116: // requred redirector
117: redirector = (ServletRedirectorService) sb.getService(this ,
118: ServletRedirectorService.class, null);
119: if (redirector == null) {
120: throw new RuntimeException(
121: "Unable to obtain ServletRedirectorService");
122: }
123:
124: // optional root-level sb
125: NodeControlService ncs = (NodeControlService) sb.getService(
126: this , NodeControlService.class, null);
127: if (ncs != null) {
128: rootsb = ncs.getRootServiceBroker();
129: sb.releaseService(this , NodeControlService.class, ncs);
130: }
131:
132: // optional naming service
133: wp = (WhitePagesService) sb.getService(this ,
134: WhitePagesService.class, null);
135: if (wp == null && log.isWarnEnabled()) {
136: log
137: .warn("Root servlet-service unable to obtain WhitePagesService");
138: }
139:
140: // figure out which node we're in
141: NodeIdentificationService nis = (NodeIdentificationService) sb
142: .getService(this , NodeIdentificationService.class, null);
143: if (nis != null) {
144: localNode = nis.getMessageAddress().getAddress();
145: sb.releaseService(this , NodeIdentificationService.class,
146: nis);
147: }
148:
149: // create our global (wp-backed) registry
150: try {
151: globReg = new NamingServerRegistry(wp);
152: } catch (Exception e) {
153: throw new RuntimeException(
154: "Unable to create naming registry", e);
155: }
156:
157: // create our local path registry
158: ServletRegistry rootReg;
159: try {
160: rootReg = new RootServletRegistry(globReg);
161: } catch (Exception e) {
162: throw new RuntimeException(
163: "Unable to create local registry", e);
164: }
165:
166: // create our root "gateway" servlet
167: Servlet rootServlet;
168: try {
169: rootServlet = new RootServlet(rootReg, localNode,
170: new WelcomeServlet(localNode), new AgentsServlet(
171: localNode, rootReg, globReg),
172: new RedirectorWrapper());
173: } catch (Exception e) {
174: throw new RuntimeException("Unable to create root servlet",
175: e);
176: }
177:
178: // set our gateway
179: try {
180: engine.setGateway(rootServlet);
181: } catch (Exception e) {
182: throw new RuntimeException("Unable to set gateway servlet",
183: e);
184: }
185:
186: // get our naming entries
187: //
188: // we do this after "setGateway(..)", in case our engine requires some
189: // side-effect of setting the gateway to figure out its entries.
190: Map namingEntries;
191: try {
192: namingEntries = engine.getNamingEntries();
193: } catch (Exception e) {
194: throw new RuntimeException(
195: "Unable to get the naming entries map", e);
196: }
197:
198: // configure the global registry with our naming entries
199: try {
200: globReg.configure(namingEntries);
201: } catch (Exception e) {
202: throw new RuntimeException(
203: "Unable to register in the name server", e);
204: }
205:
206: // create and advertise our service
207: this .rootSP = new RootServletServiceProviderImpl(rootReg,
208: namingEntries);
209: ServiceBroker the_sb = (rootsb == null ? sb : rootsb);
210: the_sb.addService(RootServletService.class, rootSP);
211: }
212:
213: public void unload() {
214:
215: // revoke our service
216: if (rootSP != null) {
217: ServiceBroker the_sb = (rootsb == null ? sb : rootsb);
218: the_sb.revokeService(RootServletService.class, rootSP);
219: rootSP = null;
220: }
221:
222: // release services
223: if (wp != null) {
224: sb.releaseService(this , WhitePagesService.class, wp);
225: wp = null;
226: }
227: if (engine != null) {
228: sb.releaseService(this , ServletEngineService.class, engine);
229: engine = null;
230: }
231: if (log != null) {
232: sb.releaseService(this , LoggingService.class, log);
233: log = null;
234: }
235:
236: super .unload();
237: }
238:
239: //
240: // inner classes:
241: //
242:
243: private class RedirectorWrapper implements Redirector {
244:
245: public void redirect(String encName, List options,
246: HttpServletRequest req, HttpServletResponse res)
247: throws ServletException, IOException {
248:
249: int status;
250: Exception error = null;
251: NamingSupportImpl namingSupport = null;
252:
253: if (redirector == null) {
254: // no redirector
255: status = ServletRedirector.NOT_SUPPORTED;
256: } else {
257: // create naming wrapper
258: namingSupport = new NamingSupportImpl();
259:
260: // attempt redirect
261: try {
262: status = redirector.redirect(encName, options,
263: namingSupport, req, res);
264: } catch (Exception e) {
265: status = ServletRedirector.OTHER_ERROR;
266: error = e;
267: e.printStackTrace();
268: }
269:
270: if (status == ServletRedirector.REDIRECTED) {
271: // success or redirector-custom error page
272: return;
273: }
274: }
275:
276: // write error page
277: int errorCode;
278: String header;
279: String message;
280: switch (status) {
281: case ServletRedirector.NOT_SUPPORTED:
282: errorCode = HttpServletResponse.SC_NOT_IMPLEMENTED;
283: header = "unsupported_redirect";
284: message = "Unsupported redirect options: " + options;
285: break;
286: case ServletRedirector.NO_NAMING_ENTRIES:
287: errorCode = HttpServletResponse.SC_NOT_FOUND;
288: header = "agent";
289: message = ("\"" + encName + "\" Not Found");
290: break;
291: case ServletRedirector.DETECTED_LOOP:
292: errorCode = HttpServletResponse.SC_NOT_FOUND;
293: header = "stale_naming";
294: message = "Detected stale naming entries that would have resulted in a"
295: + " redirect loop: "
296: + namingSupport.getNamingEntries(encName, -1);
297: break;
298: default:
299: errorCode = HttpServletResponse.SC_NOT_FOUND;
300: header = "other_error";
301: message = (error == null ? "Unspecified redirect error"
302: : "Redirect exception: " + error);
303: break;
304: }
305: res.setContentType("text/plain");
306: res.setStatus(errorCode);
307: res.addHeader("Cougaar-error", header);
308: PrintWriter out = res.getWriter();
309: out.println(message);
310: }
311:
312: private class NamingSupportImpl implements NamingSupport {
313:
314: private final Map cache = new HashMap();
315:
316: public Map getNamingEntries(String encName, long timeout) {
317: // we keep a cache so we don't block for the same timeout for every
318: // redirector attempt, plus to make sure that all our redirectors see
319: // the same naming data snapshots.
320: synchronized (cache) {
321: Object o = cache.get(encName);
322: if (o instanceof Map) {
323: // found in cached
324: return (Map) o;
325: }
326: if (o instanceof Long) {
327: // check to see if our cached timeout is longer than the new one
328: long t = ((Long) o).longValue();
329: if (t < 0) {
330: if (timeout < 0) {
331: return null;
332: }
333: } else if (t == 0) {
334: return null;
335: } else {
336: if (timeout < 0
337: || (timeout > 0 && timeout <= t)) {
338: return null;
339: }
340: }
341: }
342: // do lookup
343: Map m;
344: try {
345: m = globReg.getAll(encName, timeout);
346: } catch (Exception e) {
347: // either timeout or real exception
348: m = null;
349: }
350: // convert from name->entry(type,uri) to type->uri
351: if (m != null && !m.isEmpty()) {
352: Map m2 = new HashMap(m.size());
353: for (Iterator iter = m.values().iterator(); iter
354: .hasNext();) {
355: AddressEntry ae = (AddressEntry) iter
356: .next();
357: m2.put(ae.getType(), ae.getURI());
358: }
359: m = Collections.unmodifiableMap(m2);
360: }
361: // cache result
362: o = m;
363: if (o == null) {
364: o = new Long(timeout);
365: }
366: cache.put(encName, o);
367: // return possibly null map
368: return m;
369: }
370: }
371: }
372: }
373:
374: /**
375: * Service provider for our <code>RootServletService</code>.
376: */
377: private static class RootServletServiceProviderImpl implements
378: ServiceProvider {
379:
380: private final ServletRegistry rootReg;
381: private final int httpPort;
382: private final int httpsPort;
383:
384: public RootServletServiceProviderImpl(ServletRegistry rootReg,
385: Map namingEntries) {
386: this .rootReg = rootReg;
387: httpPort = _extractPort(namingEntries, "http", 80);
388: httpsPort = _extractPort(namingEntries, "https", 443);
389: }
390:
391: private static int _extractPort(Map m, String scheme, int deflt) {
392: if (m != null) {
393: Object o = m.get(scheme);
394: if (o instanceof URI) {
395: int port = ((URI) o).getPort();
396: return (port < 0 ? deflt : port);
397: }
398: }
399: return -1;
400: }
401:
402: public Object getService(ServiceBroker sb, Object req, Class cl) {
403: return (RootServletService.class.isAssignableFrom(cl) ? (new RootServletServiceImpl())
404: : null);
405: }
406:
407: public void releaseService(ServiceBroker sb, Object req,
408: Class cl, Object svc) {
409: // unregister all servlets of this service instance
410: ((RootServletServiceImpl) svc).unregisterAll();
411: }
412:
413: private class RootServletServiceImpl implements
414: RootServletService {
415:
416: // List of paths registered by this requestor; typically a
417: // tiny list.
418: //
419: // All the methods of this impl are synchronized on this
420: // list. Partially this is for List safety, but also
421: // it makes sense that one shouldn't be able to
422: // simultaneously register and unregister a Servlet...
423: private final List l = new ArrayList(3);
424:
425: public void register(String path, Servlet servlet)
426: throws Exception {
427: synchronized (l) {
428: rootReg.register(path, servlet);
429: l.add(path);
430: }
431: }
432:
433: public void unregister(String path) {
434: synchronized (l) {
435: rootReg.unregister(path);
436: l.remove(path);
437: }
438: }
439:
440: public void unregisterAll() {
441: // unregister all servlets
442: synchronized (l) {
443: for (int n = l.size() - 1; n >= 0; n--) {
444: String path = (String) l.get(n);
445: rootReg.unregister(path);
446: }
447: l.clear();
448: }
449: }
450:
451: public int getHttpPort() {
452: return httpPort;
453: }
454:
455: public int getHttpsPort() {
456: return httpsPort;
457: }
458: }
459: }
460: }
|