001: /*
002: * <copyright>
003: * Copyright 1999-2004 Cougaar Software, Inc.
004: * under sponsorship of the Defense Advanced Research Projects
005: * Agency (DARPA).
006: *
007: * You can redistribute this software and/or modify it under the
008: * terms of the Cougaar Open Source License as published on the
009: * Cougaar Open Source Website (www.cougaar.org).
010: *
011: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
012: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
013: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
014: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
015: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
016: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
017: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
018: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
019: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
020: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
021: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
022: *
023: * </copyright>
024: */
025:
026: package org.cougaar.mts.http;
027:
028: import java.io.IOException;
029: import java.io.ObjectInputStream;
030: import java.io.ObjectOutputStream;
031: import java.lang.reflect.Method;
032: import java.net.HttpURLConnection;
033: import java.net.InetAddress;
034: import java.net.URI;
035: import java.net.URL;
036: import java.net.UnknownHostException;
037:
038: import javax.servlet.Servlet;
039:
040: import org.cougaar.core.component.ServiceAvailableEvent;
041: import org.cougaar.core.component.ServiceAvailableListener;
042: import org.cougaar.core.component.ServiceBroker;
043: import org.cougaar.core.mts.MessageAddress;
044: import org.cougaar.core.mts.MessageAttributes;
045: import org.cougaar.core.service.BlackboardService;
046: import org.cougaar.core.service.LoggingService;
047: import org.cougaar.core.service.ServletService;
048: import org.cougaar.mts.base.CommFailureException;
049: import org.cougaar.mts.base.DestinationLink;
050: import org.cougaar.mts.base.LinkProtocol;
051: import org.cougaar.mts.base.MisdeliveredMessageException;
052: import org.cougaar.mts.base.NameLookupException;
053: import org.cougaar.mts.base.RPCLinkProtocol;
054: import org.cougaar.mts.base.UnregisteredNameException;
055: import org.cougaar.mts.std.AttributedMessage;
056:
057: /**
058: * This {@link LinkProtocol} uses the Cougaar's {@link
059: * ServletService} (Tomcat) for communication via http.
060: */
061: public class HTTPLinkProtocol extends RPCLinkProtocol {
062:
063: public final String SERVLET_URI = "/httpmts";
064:
065: /**
066: * The preferred ServletService API to get the http/https port,
067: * which we obtain through reflection to avoid a "webserver"
068: * module compile dependency.
069: */
070: private static final String ROOT_SERVLET_SERVICE_CLASS = "org.cougaar.lib.web.service.RootServletService";
071:
072: private LoggingService logger;
073: private ServletService _servletService;
074: private boolean servant_made = false;
075:
076: public void load() {
077: super .load();
078: logger = getLoggingService(); // from BoundComponent
079: }
080:
081: /**
082: * We release the ServletService here because in doing so,
083: * the ServletService.unregisterAll() is invoked.
084: */
085: public void unload() {
086: ServiceBroker sb = getServiceBroker();
087: sb.releaseService(this , ServletService.class, _servletService);
088: super .unload();
089: }
090:
091: /**
092: * Get the WP Entry Type for registering and querying for WP entries.
093: */
094: public String getProtocolType() {
095: return "-HTTP";
096: }
097:
098: /**
099: * Get the protocol to use for http connections.
100: */
101: public String getProtocol() {
102: return "http";
103: }
104:
105: /**
106: * determined the underlying socket is encrypted.
107: */
108: protected Boolean usesEncryptedSocket() {
109: return Boolean.FALSE;
110: }
111:
112: /**
113: * Returns 500 (hard-coded value less than RMI).
114: */
115: protected int computeCost(AttributedMessage message) {
116: return 500;
117: }
118:
119: protected String getPath() {
120: return SERVLET_URI;
121: }
122:
123: /**
124: * Create servlet that handle java serialized messages over HTTP.
125: */
126: protected Servlet createServlet() {
127: return new HTTPLinkProtocolServlet(getDeliverer(), logger);
128: }
129:
130: /**
131: * Create destination link to stream java serialized messages over
132: * HTTP.
133: */
134: protected DestinationLink createDestinationLink(MessageAddress addr) {
135: return new HTTPDestinationLink(addr);
136: }
137:
138: /**
139: * Register the Servlet that will handle the messages on the
140: * receiving end.
141: */
142: private void registerServlet(ServiceBroker sb) {
143: _servletService = (ServletService) sb.getService(this ,
144: ServletService.class, null);
145: try {
146: if (logger.isDebugEnabled()) {
147: logger.debug("registering " + getPath() + " with "
148: + _servletService);
149: }
150: _servletService.register(getPath(), createServlet());
151: } catch (IllegalArgumentException iae) {
152: // an IllegalArgumentException could occur if the servlet
153: // path has already been registered. for example, both
154: // the HTTP and HTTPS LinkProtocols could be installed.
155: logger.warn(getPath() + " already register.");
156: } catch (Exception e) {
157: logger.error(getPath() + " failed to register.");
158: }
159:
160: // we release the ServletService in the unload() method
161: // because in doing so, the ServletService.unregisterAll() is
162: // invoked.
163: }
164:
165: /**
166: * This function binds the url in the wp early. But the servlet
167: * at that url can't be made until the ServletService is
168: * available. This is handled by registerServlet(), which won't
169: * be called until the ServiceAvailableEvent says it's time.
170: */
171: protected void findOrMakeNodeServant() {
172: if (servant_made)
173: return;
174:
175: ServiceBroker sb = getServiceBroker();
176:
177: // use the servlet service to get our local servlet port
178: int port = -1;
179: Class ssClass;
180: try {
181: ssClass = Class.forName(ROOT_SERVLET_SERVICE_CLASS);
182: } catch (Exception e) {
183: ssClass = ServletService.class;
184: }
185: Object ss = sb.getService(this , ssClass, null);
186: if (ss != null) {
187: // port = ss.get<Protocol>Port();
188: try {
189: String s = getProtocol();
190: s = Character.toUpperCase(s.charAt(0)) + s.substring(1);
191: s = "get" + s + "Port";
192: Method m = ssClass.getMethod(s, new Class[] {});
193: Object ret = m.invoke(ss, new Object[] {});
194: port = ((Integer) ret).intValue();
195: } catch (Exception e) {
196: if (logger.isWarnEnabled()) {
197: logger.warn("Unable to get " + getProtocol()
198: + " port", e);
199: }
200: }
201: sb.releaseService(this , ssClass, ss);
202: }
203: if (port < 0) {
204: if (logger.isWarnEnabled()) {
205: logger.warn(getProtocol() + " port is disabled");
206: }
207: }
208:
209: MessageAddress node_addr = getNameSupport()
210: .getNodeMessageAddress();
211: String node_name = node_addr.toAddress();
212: try {
213: InetAddress me = InetAddress.getLocalHost();
214: URI nodeURI = new URI(getProtocol() + "://"
215: + me.getHostName() + ':' + port + "/$" + node_name
216: + getPath());
217: setNodeURI(nodeURI);
218: } catch (Exception e) {
219: e.printStackTrace();
220: }
221:
222: // Call registerServlet only if/when BlackboardService is
223: // available. The BlackboardService is required because we
224: // want to register our servlet with that ServiceBroker.
225: if (sb.hasService(BlackboardService.class)) {
226: registerServlet(sb);
227: } else {
228: sb.addServiceListener(new ServiceAvailableListener() {
229: public void serviceAvailable(ServiceAvailableEvent ae) {
230: Class svc_class = ae.getService();
231: if (BlackboardService.class
232: .isAssignableFrom(svc_class)) {
233: ServiceBroker svc_sb = ae.getServiceBroker();
234: registerServlet(svc_sb);
235: svc_sb.removeServiceListener(this );
236: }
237: }
238: });
239: }
240:
241: servant_made = true;
242:
243: }
244:
245: /**
246: * Servlets handle the new-address case automatically, so this is
247: * a no-op.
248: */
249: protected void remakeNodeServant() {
250: }
251:
252: protected class HTTPDestinationLink extends Link {
253:
254: public HTTPDestinationLink(MessageAddress target) {
255: super (target);
256: }
257:
258: public Class getProtocolClass() {
259: return HTTPLinkProtocol.this .getClass();
260: }
261:
262: protected Object decodeRemoteRef(URI ref) throws Exception {
263: if (ref == null)
264: return null;
265: else
266: return ref.toURL();
267: }
268:
269: /**
270: * Posts the message to the target Agent's HTTP Link Protocol Servlet.
271: */
272: protected MessageAttributes forwardByProtocol(
273: Object remote_ref, AttributedMessage message)
274: throws NameLookupException, UnregisteredNameException,
275: CommFailureException, MisdeliveredMessageException {
276: try {
277: Object response = postMessage((URL) remote_ref, message);
278: if (response instanceof MessageAttributes) {
279: return (MessageAttributes) response;
280: } else if (response instanceof MisdeliveredMessageException) {
281: decache();
282: throw (MisdeliveredMessageException) response;
283: } else {
284: throw new CommFailureException((Exception) response);
285: }
286: } catch (Exception e) {
287: //e.printStackTrace();
288: throw new CommFailureException(e);
289: }
290: }
291:
292: /**
293: * This method streams serialized java objects over HTTP, and
294: * could be overridden if streaming format is different (e.g.,
295: * SOAP)
296: */
297: protected Object postMessage(URL url, AttributedMessage message)
298: throws IOException, ClassNotFoundException,
299: UnknownHostException {
300: ObjectInputStream ois = null;
301: ObjectOutputStream out = null;
302: try {
303: if (logger.isDebugEnabled()) {
304: logger.debug("sending "
305: + message.getRawMessage().getClass()
306: .getName() + "("
307: + message.getOriginator() + "->"
308: + message.getTarget() + ") to " + url);
309: }
310: // NOTE: Performing a URL.openConnection() does not
311: // necessarily open a new socket. Specifically,
312: // HttpUrlConnection reuses a previously opened socket
313: // to the target, and there is no way to force the
314: // underlying socket to close. From the javadoc:
315: // "Calling the disconnect() method may close the
316: // underlying socket if a persistent connection is
317: // otherwise idle at that time."
318: //
319: // However, This could pose a resource consumption
320: // issue. If this is the case, we need to use a
321: // different HTTP Client implementation such as
322: // Jakarta's Common HTTP Client.
323: HttpURLConnection conn = (HttpURLConnection) url
324: .openConnection();
325: // Don't follow redirects automatically.
326: conn.setInstanceFollowRedirects(false);
327: // Let the system know that we want to do output
328: conn.setDoOutput(true);
329: // Let the system know that we want to do input
330: conn.setDoInput(true);
331: // No caching, we want the real thing
332: conn.setUseCaches(false);
333: // Specify the content type
334: conn.setRequestProperty("Content-Type",
335: "application/x-www-form-urlencoded");
336: conn.setRequestMethod("POST");
337: // write object to output stream
338: out = new ObjectOutputStream(conn.getOutputStream());
339: out.writeObject(message);
340: out.flush();
341:
342: // get response
343: ois = new ObjectInputStream(conn.getInputStream());
344: return ois.readObject();
345: } catch (Exception e) {
346: if (logger.isWarnEnabled())
347: logger.warn("Exception in postMessge", e);
348: } finally {
349: if (out != null) {
350: out.close();
351: }
352: if (ois != null) {
353: ois.close();
354: }
355: }
356: return null;
357: }
358:
359: }
360:
361: protected void releaseNodeServant() {
362: }
363: }
|