/*
* Copyright (c) 2001-2007 Sun Microsystems, Inc. All rights reserved.
*
* The Sun Project JXTA(TM) Software License
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The end-user documentation included with the redistribution, if any, must
* include the following acknowledgment: "This product includes software
* developed by Sun Microsystems, Inc. for JXTA(TM) technology."
* Alternately, this acknowledgment may appear in the software itself, if
* and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must
* not be used to endorse or promote products derived from this software
* without prior written permission. For written permission, please contact
* Project JXTA at http://www.jxta.org.
*
* 5. Products derived from this software may not be called "JXTA", nor may
* "JXTA" appear in their name, without prior written permission of Sun.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN
* MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* JXTA is a registered trademark of Sun Microsystems, Inc. in the United
* States and other countries.
*
* Please see the license information page at :
* <http://www.jxta.org/project/www/license.html> for instructions on use of
* the license in source files.
*
* ====================================================================
*
* This software consists of voluntary contributions made by many individuals
* on behalf of Project JXTA. For more information on Project JXTA, please see
* http://www.jxta.org.
*
* This license is based on the BSD license adopted by the Apache Foundation.
*/
package net.jxta.impl.endpoint.servlethttp;
import net.jxta.document.Advertisement;
import net.jxta.document.AdvertisementFactory;
import net.jxta.document.Attribute;
import net.jxta.document.XMLElement;
import net.jxta.endpoint.EndpointAddress;
import net.jxta.endpoint.EndpointService;
import net.jxta.exception.PeerGroupException;
import net.jxta.id.ID;
import net.jxta.impl.endpoint.IPUtils;
import net.jxta.impl.endpoint.transportMeter.TransportBindingMeter;
import net.jxta.impl.endpoint.transportMeter.TransportMeter;
import net.jxta.impl.endpoint.transportMeter.TransportMeterBuildSettings;
import net.jxta.impl.endpoint.transportMeter.TransportServiceMonitor;
import net.jxta.impl.meter.MonitorManager;
import net.jxta.impl.peergroup.GenericPeerGroup;
import net.jxta.impl.protocol.HTTPAdv;
import net.jxta.logging.Logger;
import net.jxta.logging.Logging;
import net.jxta.meter.MonitorResources;
import net.jxta.peergroup.PeerGroup;
import net.jxta.platform.Module;
import net.jxta.protocol.ConfigParams;
import net.jxta.protocol.ModuleImplAdvertisement;
import net.jxta.protocol.TransportAdvertisement;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.List;
/**
* A JXTA Message Transport
*
* <p/>This class is really a facade for the following:<ul>
* <li>An HTTP client message sender</li>
* <li>An HTTP-server-based message receiver</li>
* </ul>
*/
public final class ServletHttpTransportImpl implements ServletHttpTransport, Module {
private final static transient Logger LOG = Logging.getLogger(ServletHttpTransportImpl.class.getName());
/**
* The name of the protocol
*/
private final static String DEFAULT_HTTP_PROTOCOL_NAME = "http";
String HTTP_PROTOCOL_NAME = DEFAULT_HTTP_PROTOCOL_NAME;
/**
* PeerGroup we are working for
*/
PeerGroup group;
ID assignedID;
ModuleImplAdvertisement implAdvertisement;
/**
* The endpoint we attach to.
*/
private EndpointService endpoint = null;
/**
* If {@code true} then we are configured to act as an HTTP client. This
* means we will poll for messages.
*/
private boolean configClient = false;
/**
* If {@code true} then we are configured to act as an HTTP server. This
* means we will accept connections from polling clients.
*/
private boolean configServer = false;
/**
* The HttpMessageSender instance
*/
private HttpMessageSender sender = null;
/**
* The HttpMessageReceiver instance
*/
private HttpMessageReceiver receiver = null;
/**
* The TransportMeter for this httpTransport
*/
private TransportMeter transportMeter;
/**
* The TransportBindingMeter for unknown connections (pings/errors)
*/
private TransportBindingMeter unknownTransportBindingMeter;
/**
* The InetAddress of the network interface we are bound to otherwise the
* ALL/ANY address.
*/
InetAddress usingInterface = null;
/**
* Port number to which we are bound.
*/
int usingPort = 0;
/**
* The addresses which we will advertise and by which we may be reached.
*/
private List<EndpointAddress> publicAddresses = null;
/**
* Our preferred return address which we will use as the "source" for
* messages we send.
*/
private EndpointAddress publicAddress;
/**
* {@inheritDoc}
*/
public synchronized void init(PeerGroup group, ID assignedID, Advertisement impl) throws PeerGroupException {
this.group = group;
this.assignedID = assignedID;
implAdvertisement = (ModuleImplAdvertisement) impl;
// Get out invariable parameters from the implAdv
XMLElement param = (XMLElement) implAdvertisement.getParam();
if (param != null) {
Enumeration list = param.getChildren("Proto");
if (list.hasMoreElements()) {
XMLElement pname = (XMLElement) list.nextElement();
String configProtoName = pname.getTextValue();
if (null != configProtoName) {
HTTP_PROTOCOL_NAME = configProtoName.trim();
}
}
}
ConfigParams peerAdv = group.getConfigAdvertisement();
param = (XMLElement) peerAdv.getServiceParam(assignedID);
Enumeration httpChilds = param.getChildren(TransportAdvertisement.getAdvertisementType());
// get the TransportAdv
if (httpChilds.hasMoreElements()) {
param = (XMLElement) httpChilds.nextElement();
Attribute typeAttr = param.getAttribute("type");
if (!HTTPAdv.getAdvertisementType().equals(typeAttr.getValue())) {
throw new IllegalArgumentException("Transport adv is not an http adv");
}
if (httpChilds.hasMoreElements()) {
throw new IllegalArgumentException("Configuration contained multiple http advertisements");
}
} else {
throw new IllegalArgumentException("Configuration did not contain http advertisement");
}
Advertisement paramsAdv = AdvertisementFactory.newAdvertisement(param);
if (!(paramsAdv instanceof HTTPAdv)) {
throw new IllegalArgumentException("Provided advertisement was not a " + HTTPAdv.getAdvertisementType());
}
HTTPAdv httpAdv = (HTTPAdv) paramsAdv;
// determine the local interface to use. If the user specifies
// one, use that. Otherwise, use the all the available interfaces.
String interfaceAddressStr = httpAdv.getInterfaceAddress();
boolean publicAddressOnly = httpAdv.getPublicAddressOnly();
if (interfaceAddressStr != null) {
try {
usingInterface = InetAddress.getByName(interfaceAddressStr);
} catch (UnknownHostException failed) {
Logging.logCheckedWarning(LOG, "Invalid address for local interface address, using default");
usingInterface = IPUtils.ANYADDRESS;
}
} else {
usingInterface = IPUtils.ANYADDRESS;
}
usingPort = httpAdv.getPort();
configClient = httpAdv.isClientEnabled();
configServer = httpAdv.isServerEnabled();
publicAddresses = getPublicAddresses(configServer, httpAdv.getServer(), usingInterface, usingPort, publicAddressOnly);
if (!configClient && !configServer) {
throw new IllegalArgumentException("Neither incoming nor outgoing connections configured.");
}
publicAddress = publicAddresses.get(0);
// Tell tell the world about our configuration.
if (Logging.SHOW_CONFIG && LOG.isConfigEnabled()) {
StringBuilder configInfo = new StringBuilder("Configuring HTTP Message Transport : " + assignedID);
if (implAdvertisement != null) {
configInfo.append("\n\tImplementation:");
configInfo.append("\n\t\tModule Spec ID: ").append(implAdvertisement.getModuleSpecID());
configInfo.append("\n\t\tImpl Description: ").append(implAdvertisement.getDescription());
configInfo.append("\n\t\tImpl URI: ").append(implAdvertisement.getUri());
configInfo.append("\n\t\tImpl Code: ").append(implAdvertisement.getCode());
}
configInfo.append("\n\tGroup Params:");
configInfo.append("\n\t\tGroup: ").append(group.getPeerGroupName());
configInfo.append("\n\t\tGroup ID: ").append(group.getPeerGroupID());
configInfo.append("\n\t\tPeer ID: ").append(group.getPeerID());
configInfo.append("\n\tConfiguration :");
configInfo.append("\n\t\tProtocol: ").append(httpAdv.getProtocol());
configInfo.append("\n\t\tClient Enabled: ").append(configClient);
configInfo.append("\n\t\tServer Enabled: ").append(configServer);
configInfo.append("\n\t\tPublic address: ").append(httpAdv.getServer() == null ? "(unspecified)" : httpAdv.getServer());
configInfo.append("\n\t\tInterface address: ").append(interfaceAddressStr == null ? "(unspecified)" : interfaceAddressStr);
configInfo.append("\n\t\tUnicast Server Bind Addr: ").append(IPUtils.getHostAddress(usingInterface)).append(":").append(usingPort);
configInfo.append("\n\t\tPublic Addresses: ");
configInfo.append("\n\t\t\tDefault Endpoint Addr : ").append(publicAddress);
for (EndpointAddress anAddr : publicAddresses) {
configInfo.append("\n\t\t\tEndpoint Addr : ").append(anAddr);
}
LOG.config(configInfo.toString());
}
}
/**
* {@inheritDoc}
*/
public synchronized int startApp(String[] args) {
endpoint = group.getEndpointService();
if (null == endpoint) {
Logging.logCheckedWarning(LOG, "Stalled until there is an endpoint service");
return START_AGAIN_STALLED;
}
if (TransportMeterBuildSettings.TRANSPORT_METERING) {
TransportServiceMonitor transportServiceMonitor = (TransportServiceMonitor) MonitorManager.getServiceMonitor(group,
MonitorResources.transportServiceMonitorClassID);
if (transportServiceMonitor != null) {
transportMeter = transportServiceMonitor.createTransportMeter("HTTP", publicAddress);
unknownTransportBindingMeter = transportMeter.getTransportBindingMeter(TransportMeter.UNKNOWN_PEER,
TransportMeter.UNKNOWN_ADDRESS);
}
}
if (configServer) {
// Start the http server that runs the receiver.
try {
// Use peer group class loader (useful for HttpMessageServlet)
final ClassLoader classLoader = GenericPeerGroup.getLoader().getClassLoader();
receiver = new HttpMessageReceiver(this, publicAddresses, usingInterface, usingPort, classLoader);
receiver.start();
} catch (PeerGroupException e) {
Logging.logCheckedError(LOG, "Could not start http message receiver\n", e);
return -1; // Can't go on; if we were configured to be a server we must make the failure obvious.
}
}
if (configClient) {
// create the MessageSender
try {
sender = new HttpMessageSender(this,
new EndpointAddress("jxta", group.getPeerID().getUniqueValue().toString(), null, null));
sender.start();
} catch (PeerGroupException e) {
Logging.logCheckedError(LOG, "Could not start http message sender\n", e);
return -1; // Can't go on; if we were configured to be a client we must make the failure obvious.
}
}
return 0;
}
/**
* {@inheritDoc}
*/
public synchronized void stopApp() {
if (receiver != null) {
receiver.stop();
}
if (sender != null) {
sender.stop();
}
endpoint = null;
}
/**
* {@inheritDoc}
*/
public PeerGroup getPeerGroup() {
return group;
}
/**
* {@inheritDoc}
*/
public ID getAssignedID() {
return assignedID;
}
/**
* {@inheritDoc}
*/
public String getConfiguredHttpProtocolName() {
return HTTP_PROTOCOL_NAME;
}
/**
* {@inheritDoc}
*/
public EndpointService getEndpointService() {
return endpoint;
}
private List<EndpointAddress> getPublicAddresses(boolean serverEnabled, String serverName, InetAddress usingInterface, int serverSocketPort, boolean publicAddressOnly) {
List<EndpointAddress> publicAddresses = new ArrayList<EndpointAddress>();
if (serverEnabled) {
// Build the publicAddresses
// first in the list is the "public server name". We don't try to
// resolve this since it might not be resolvable in the context
// we are running in, we just assume it's good.
if (serverName != null) {
// use speced server name.
EndpointAddress newAddr = new EndpointAddress(HTTP_PROTOCOL_NAME, serverName, null, null);
publicAddresses.add(newAddr);
if (publicAddressOnly) {
return publicAddresses;
}
}
}
// then add the rest of the local interfaces as appropriate
if (usingInterface.equals(IPUtils.ANYADDRESS)) {
// its wildcarded
List<EndpointAddress> wildAddrs = new ArrayList<EndpointAddress>();
for (InetAddress anAddress : IPUtils.getAllLocalAddresses()) {
String hostAddress = IPUtils.getHostAddress(anAddress);
EndpointAddress newAddr = new EndpointAddress(HTTP_PROTOCOL_NAME,
hostAddress + ":" + Integer.toString(serverSocketPort), null, null);
// don't add it if its already in the list
if (!publicAddresses.contains(newAddr)) {
wildAddrs.add(newAddr);
}
}
// we sort them so that later equals() will be deterministic.
// the result of IPUtils.getAllLocalAddresses() is not known
// to be sorted.
Collections.sort(wildAddrs, new Comparator<EndpointAddress>() {
public int compare(EndpointAddress one, EndpointAddress two) {
return one.toString().compareTo(two.toString());
}
@Override
public boolean equals(Object that) {
return this == that;
}
});
publicAddresses.addAll(wildAddrs);
} else {
// use specified interface
String hostAddress = IPUtils.getHostAddress(usingInterface);
EndpointAddress newAddr = new EndpointAddress(HTTP_PROTOCOL_NAME,
hostAddress + ":" + Integer.toString(serverSocketPort), null, null);
// don't add it if its already in the list
if (!publicAddresses.contains(newAddr)) {
publicAddresses.add(newAddr);
}
}
return publicAddresses;
}
/**
* {@inheritDoc}
*/
public TransportBindingMeter getTransportBindingMeter(String peerIDString, EndpointAddress destinationAddress) {
if (transportMeter != null) {
return transportMeter.getTransportBindingMeter((peerIDString != null) ? peerIDString : TransportMeter.UNKNOWN_PEER,
destinationAddress);
} else {
return null;
}
}
TransportBindingMeter getUnknownTransportBindingMeter() {
return unknownTransportBindingMeter;
}
}