/**
* Copyright (c) 2009, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.synapse.transport.passthru;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.axiom.om.OMElement;
import org.apache.axis2.AxisFault;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.context.SessionContext;
import org.apache.axis2.description.AxisModule;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.AxisServiceGroup;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.description.TransportInDescription;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.axis2.engine.AxisEvent;
import org.apache.axis2.engine.AxisObserver;
import org.apache.axis2.transport.TransportListener;
import org.apache.axis2.transport.base.BaseConstants;
import org.apache.axis2.transport.base.BaseUtils;
import org.apache.axis2.transport.base.threads.NativeThreadFactory;
import org.apache.axis2.transport.base.threads.WorkerPool;
import org.apache.axis2.transport.base.tracker.AxisServiceFilter;
import org.apache.axis2.transport.base.tracker.AxisServiceTracker;
import org.apache.axis2.transport.base.tracker.AxisServiceTrackerListener;
import org.apache.axis2.util.JavaUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpHost;
import org.apache.http.impl.nio.reactor.DefaultListeningIOReactor;
import org.apache.http.nio.reactor.IOReactorException;
import org.apache.http.nio.reactor.IOReactorExceptionHandler;
import org.apache.synapse.transport.http.conn.Scheme;
import org.apache.synapse.transport.http.conn.ServerConnFactory;
import org.apache.synapse.transport.nhttp.config.ServerConnFactoryBuilder;
import org.apache.synapse.transport.passthru.config.PassThroughConfiguration;
import org.apache.synapse.transport.passthru.config.SourceConfiguration;
import org.apache.synapse.transport.passthru.core.PassThroughSharedListenerConfiguration;
import org.apache.synapse.transport.passthru.core.PassThroughListeningIOReactorManager;
import org.apache.synapse.transport.passthru.jmx.MBeanRegistrar;
import org.apache.synapse.transport.passthru.jmx.PassThroughTransportMetricsCollector;
import org.apache.synapse.transport.passthru.jmx.TransportView;
import org.apache.synapse.transport.passthru.util.ActiveConnectionMonitor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* This is the TransportListener listening for incoming connections. This class start the IOReactor
* and registers the InRequest Handlers on it.
*/
public class PassThroughHttpListener implements TransportListener {
protected Log log = LogFactory.getLog(this.getClass());
/** The reactor being used */
private DefaultListeningIOReactor ioReactor;
/**
* IOReactor Manager
*/
private PassThroughListeningIOReactorManager passThroughListeningIOReactorManager;
/** The protocol handler */
private SourceHandler handler;
/** The connection factory */
private ServerConnFactory connFactory;
/** The protocol scheme of the listener*/
private Scheme scheme;
/** The configuration of the listener */
private SourceConfiguration sourceConfiguration = null;
/** The custom URI map for the services if there are any */
private Map<String, String> serviceNameToEPRMap = new HashMap<String, String>();
/** The service name map for the custom URI if there are any */
private Map<String, String> eprToServiceNameMap = new HashMap<String, String>();
/** the axis observer that gets notified of service life cycle events*/
//private final AxisObserver axisObserver = new GenericAxisObserver();
private volatile int state = BaseConstants.STOPPED;
private String namePrefix;
/** Active Connection Monitor Scheduler **/
private final ScheduledExecutorService activeConnectionMonitorScheduler = Executors.newSingleThreadScheduledExecutor();
/** Delay for ActiveConnectionMonitor **/
public static final long ACTIVE_CONNECTION_MONITOR_DELAY = 1000;
/** ServiceTracker to receive updates on services for lifetime management **/
private AxisServiceTracker serviceTracker;
private TransportInDescription pttInDescription;
/**
* HttpListener Running port
*/
private int operatingPort;
protected Scheme initScheme() {
return new Scheme("http", 80, false);
}
protected ServerConnFactoryBuilder initConnFactoryBuilder(
final TransportInDescription transportIn, final HttpHost host) throws AxisFault {
return new ServerConnFactoryBuilder(transportIn, host);
}
public void init(ConfigurationContext cfgCtx, TransportInDescription transportInDescription)
throws AxisFault {
log.info("Initializing Pass-through HTTP/S Listener...");
pttInDescription = transportInDescription;
namePrefix = transportInDescription.getName().toUpperCase(Locale.US);
scheme = initScheme();
int portOffset = Integer.parseInt(System.getProperty("portOffset", "0"));
Parameter portParam = transportInDescription.getParameter("port");
int port = Integer.parseInt(portParam.getValue().toString());
operatingPort = port + portOffset;
portParam.setValue(String.valueOf(operatingPort));
portParam.getParameterElement().setText(String.valueOf(operatingPort));
System.setProperty(transportInDescription.getName() + ".nio.port", String.valueOf(operatingPort));
Object obj = cfgCtx.getProperty(PassThroughConstants.PASS_THROUGH_TRANSPORT_WORKER_POOL);
WorkerPool workerPool = null;
if (obj != null) {
workerPool = (WorkerPool) obj;
}
PassThroughTransportMetricsCollector metrics = new PassThroughTransportMetricsCollector(
true, scheme.getName());
TransportView view = new TransportView(this, null, metrics, null);
MBeanRegistrar.getInstance().registerMBean(
view, "Transport",
"passthru-" + namePrefix.toLowerCase() + "-receiver");
sourceConfiguration = new SourceConfiguration(cfgCtx, transportInDescription, scheme, workerPool, metrics);
sourceConfiguration.build();
HttpHost host = new HttpHost(
sourceConfiguration.getHostname(),
sourceConfiguration.getPort(),
sourceConfiguration.getScheme().getName());
ServerConnFactoryBuilder connFactoryBuilder = initConnFactoryBuilder(transportInDescription, host);
connFactory = connFactoryBuilder.build(sourceConfiguration.getHttpParams());
handler = new SourceHandler(sourceConfiguration);
passThroughListeningIOReactorManager = PassThroughListeningIOReactorManager.getInstance();
// register to receive updates on services for lifetime management
//cfgCtx.getAxisConfiguration().addObservers(axisObserver);
String prefix = namePrefix + "-Listener I/O dispatcher";
try {
ioReactor = (DefaultListeningIOReactor) passThroughListeningIOReactorManager.
initIOReactor(operatingPort,handler,
new PassThroughSharedListenerConfiguration(
new NativeThreadFactory(new ThreadGroup(prefix + " thread group"), prefix),
connFactory, sourceConfiguration));
} catch (IOReactorException e) {
handleException("Error initiating " + namePrefix + " ListeningIOReactor", e);
}
Map<String, String> o = (Map<String, String>) cfgCtx.getProperty(PassThroughConstants.EPR_TO_SERVICE_NAME_MAP);
if (o != null) {
this.eprToServiceNameMap = o;
} else {
eprToServiceNameMap = new HashMap<String, String>();
cfgCtx.setProperty(PassThroughConstants.EPR_TO_SERVICE_NAME_MAP, eprToServiceNameMap);
}
cfgCtx.setProperty(PassThroughConstants.PASS_THROUGH_TRANSPORT_WORKER_POOL,
sourceConfiguration.getWorkerPool());
/* register to receive updates on services */
serviceTracker = new AxisServiceTracker(
cfgCtx.getAxisConfiguration(),
new AxisServiceFilter() {
public boolean matches(AxisService service) {
return !service.getName().startsWith("__") // these are "private" services
&& BaseUtils.isUsingTransport(service, pttInDescription.getName());
}
},
new AxisServiceTrackerListener() {
public void serviceAdded(AxisService service) {
addToServiceURIMap(service);
}
public void serviceRemoved(AxisService service) {
removeServiceFfromURIMap(service);
}
});
}
public void start() throws AxisFault {
serviceTracker.start();
log.info("Starting Pass-through " + namePrefix + " Listener...");
String prefix = namePrefix + "-Listener I/O dispatcher";
passThroughListeningIOReactorManager.startIOReactor(ioReactor,
passThroughListeningIOReactorManager.getServerIODispatch(operatingPort), prefix);
ioReactor.setExceptionHandler(new IOReactorExceptionHandler() {
public boolean handle(IOException ioException) {
log.warn("System may be unstable: " + namePrefix +
" ListeningIOReactor encountered a checked exception : " +
ioException.getMessage(), ioException);
return true;
}
public boolean handle(RuntimeException runtimeException) {
log.warn("System may be unstable: " + namePrefix +
" ListeningIOReactor encountered a runtime exception : "
+ runtimeException.getMessage(), runtimeException);
return true;
}
});
if(sourceConfiguration.getHttpGetRequestProcessor() != null){
sourceConfiguration.getHttpGetRequestProcessor().init(sourceConfiguration.getConfigurationContext(), handler);
}
startEndpoints();
state = BaseConstants.STARTED;
}
private void startEndpoints() throws AxisFault {
Set<InetSocketAddress> addressSet = new HashSet<InetSocketAddress>();
addressSet.addAll(connFactory.getBindAddresses());
if(PassThroughConfiguration.getInstance().getMaxActiveConnections() != -1) {
addMaxActiveConnectionCountController(PassThroughConfiguration.getInstance().getMaxActiveConnections());
}
if (addressSet.isEmpty()) {
addressSet.add(new InetSocketAddress(Integer.parseInt((String) pttInDescription.getParameter("port").getValue())));
}
// Ensure simple but stable order
List<InetSocketAddress> addressList = new ArrayList<InetSocketAddress>(addressSet);
Collections.sort(addressList, new Comparator<InetSocketAddress>() {
public int compare(InetSocketAddress a1, InetSocketAddress a2) {
String s1 = a1.toString();
String s2 = a2.toString();
return s1.compareTo(s2);
}
});
for (InetSocketAddress address: addressList) {
passThroughListeningIOReactorManager.startPTTEndpoint(address, ioReactor, namePrefix);
}
InetAddress bindAddress;
Parameter bindParam = pttInDescription.getParameter("bind-address");
if (bindParam != null) {
try {
bindAddress = InetAddress.getByName((String) bindParam.getValue());
} catch (UnknownHostException ex) {
throw AxisFault.makeFault(ex);
}
if (bindAddress != null) {
Parameter portParam = pttInDescription.getParameter("port");
int port = Integer.parseInt(portParam.getValue().toString());
addressSet.add(new InetSocketAddress(bindAddress, port));
}
}
}
/**
* Start specified end points for given set of bind addresses
*
* @param bindAddresses InetSocketAddress list to be started
* @throws AxisFault
*/
private void startSpecificEndpoints(Set<InetSocketAddress> bindAddresses) throws AxisFault {
if (PassThroughConfiguration.getInstance().getMaxActiveConnections() != -1) {
addMaxActiveConnectionCountController(PassThroughConfiguration.getInstance().getMaxActiveConnections());
}
List<InetSocketAddress> addressList = new ArrayList<InetSocketAddress>(bindAddresses);
Collections.sort(addressList, new Comparator<InetSocketAddress>() {
public int compare(InetSocketAddress a1, InetSocketAddress a2) {
String s1 = a1.toString();
String s2 = a2.toString();
return s1.compareTo(s2);
}
});
for (InetSocketAddress address : addressList) {
passThroughListeningIOReactorManager.startPTTEndpoint(address, ioReactor, namePrefix);
}
}
private void handleException(String s, Exception e) throws AxisFault {
log.error(s, e);
throw new AxisFault(s, e);
}
/**
* Pauses the IOReactor if maxActive count exceeds the configured value.
* @param maxActiveConnections
*/
private void addMaxActiveConnectionCountController(int maxActiveConnections) {
ActiveConnectionMonitor activeConnectionMonitor = new ActiveConnectionMonitor(sourceConfiguration.getMetrics(), ioReactor, maxActiveConnections);
activeConnectionMonitorScheduler.scheduleWithFixedDelay(activeConnectionMonitor, 0, ACTIVE_CONNECTION_MONITOR_DELAY, TimeUnit.MILLISECONDS);
}
public EndpointReference getEPRForService(String serviceName, String ip) throws AxisFault {
String trailer = "";
//Strip out the operation name
if (serviceName.indexOf('/') != -1) {
trailer += serviceName.substring(serviceName.indexOf("/"));
serviceName = serviceName.substring(0, serviceName.indexOf('/'));
}
// strip out the endpoint name if present
if (serviceName.indexOf('.') != -1) {
trailer += serviceName.substring(serviceName.indexOf("."));
serviceName = serviceName.substring(0, serviceName.indexOf('.'));
}
if (serviceNameToEPRMap.containsKey(serviceName)) {
return new EndpointReference(
sourceConfiguration.getCustomEPRPrefix() +
serviceNameToEPRMap.get(serviceName) + trailer);
} else {
return new EndpointReference(sourceConfiguration.getServiceEPRPrefix() +
serviceName + trailer);
}
}
/**
* Return the EPRs for the given service over this transport
* @param serviceName name of the service
* @param ip IP address
* @return the EndpointReferences for this service over the transport
* @throws AxisFault on error
*/
public EndpointReference[] getEPRsForService(String serviceName, String ip) throws AxisFault {
String trailer = "";
boolean isServiceWithCustomURI = isServiceWithCustomURI(serviceName);
//Strip out the operation name
if (serviceName.indexOf('/') != -1) {
trailer += serviceName.substring(serviceName.indexOf("/"));
serviceName = serviceName.substring(0, serviceName.indexOf('/'));
}
// strip out the endpoint name if present
if (serviceName.indexOf('.') != -1 && !isServiceWithCustomURI) {
trailer += serviceName.substring(serviceName.indexOf("."));
serviceName = serviceName.substring(0, serviceName.indexOf('.'));
}else if(isServiceWithCustomURI){
serviceName = getServiceNameFromServiceWithCustomURI(serviceName);
}
EndpointReference[] endpointReferences = new EndpointReference[1];
if (serviceNameToEPRMap.containsKey(serviceName)) {
endpointReferences[0] = new EndpointReference(
sourceConfiguration.getCustomEPRPrefix() +
serviceNameToEPRMap.get(serviceName) + trailer);
} else {
endpointReferences[0]
= new EndpointReference(sourceConfiguration.getServiceEPRPrefix() +
serviceName + trailer);
}
return endpointReferences;
}
public SessionContext getSessionContext(MessageContext messageContext) {
return null;
}
public void stop() throws AxisFault {
if (state == BaseConstants.STOPPED) return;
log.info("Stopping Pass-through " + namePrefix + " Listener..");
try {
int wait = PassThroughConfiguration.getInstance().getListenerShutdownWaitTime();
if (wait > 0) {
passThroughListeningIOReactorManager.pauseIOReactor(operatingPort);
log.info("Waiting " + wait/1000 + " seconds to cleanup active connections...");
Thread.sleep(wait);
passThroughListeningIOReactorManager.shutdownIOReactor(operatingPort, wait);
} else {
passThroughListeningIOReactorManager.shutdownIOReactor(operatingPort);
}
serviceTracker.stop();
} catch (IOException e) {
handleException("Error shutting down " + namePrefix + " listening IO reactor", e);
} catch (InterruptedException e) {
handleException("Error waiting for connection drain", e);
} finally {
state = BaseConstants.STOPPED;
}
}
public void destroy() {
log.info("Destroying PassThroughHttpListener");
/* sourceConfiguration.getConfigurationContext().
getAxisConfiguration().getObserversList().remove(axisObserver);*/
// serviceTracker.stop();
sourceConfiguration.getMetrics().destroy();
}
/**
* Pause the listener - Stops accepting new connections, but continues processing existing
* connections until they complete. This helps bring an instance into a maintenance mode
*
* @throws AxisFault if pausing fails
*/
public void pause() throws AxisFault {
if (state != BaseConstants.STARTED) return;
try {
passThroughListeningIOReactorManager.pauseIOReactor(operatingPort);
state = BaseConstants.PAUSED;
log.info(namePrefix + " Listener Paused");
} catch (IOException e) {
handleException("Error pausing IOReactor", e);
}
}
/**
* Resume the lister - Brings the lister into active mode back from a paused state
*
* @throws AxisFault if the resume fails
*/
public void resume() throws AxisFault {
if (state != BaseConstants.PAUSED) return;
try {
passThroughListeningIOReactorManager.resumeIOReactor(operatingPort);
state = BaseConstants.STARTED;
log.info(namePrefix + " Listener Resumed");
} catch (IOException e) {
handleException("Error resuming IOReactor", e);
}
}
public void reload(final TransportInDescription transportIn) throws AxisFault {
if (state != BaseConstants.STARTED) return;
// Close all listener endpoints and stop accepting new connections
passThroughListeningIOReactorManager.closeAllPTTListenerEndpoints(operatingPort);
// Rebuild connection factory
HttpHost host = new HttpHost(
sourceConfiguration.getHostname(),
sourceConfiguration.getPort(),
sourceConfiguration.getScheme().getName());
ServerConnFactoryBuilder connFactoryBuilder = initConnFactoryBuilder(transportIn, host);
connFactory = connFactoryBuilder.build(sourceConfiguration.getHttpParams());
passThroughListeningIOReactorManager.getServerIODispatch(operatingPort).update(connFactory);
startEndpoints();
log.info(namePrefix + " Reloaded");
}
/**
* Re-load specific end points given in Transport In Description
*
* @param transportIn TransportInDescriptions of the new configuration
* @throws AxisFault
*/
public void reloadSpecificEndPoints(final TransportInDescription transportIn) throws AxisFault {
if (state != BaseConstants.STARTED) {
return;
}
HttpHost host = new HttpHost(
sourceConfiguration.getHostname(),
sourceConfiguration.getPort(),
sourceConfiguration.getScheme().getName());
// Rebuild connection factory
ServerConnFactoryBuilder connFactoryBuilder = initConnFactoryBuilder(transportIn, host);
connFactory = connFactoryBuilder.build(sourceConfiguration.getHttpParams());
// Close listener endpoints and stop accepting new connections
passThroughListeningIOReactorManager.closeSpecificPTTListenerEndpoints(operatingPort, connFactory.getBindAddresses());
passThroughListeningIOReactorManager.getServerIODispatch(operatingPort).update(connFactory);
//start end points from new configuration
startSpecificEndpoints(connFactory.getBindAddresses());
log.info(namePrefix + " Reloaded");
}
/**
* Stop accepting new connections, and wait the maximum specified time for in-flight
* requests to complete before a controlled shutdown for maintenance
*
* @param milliSecs number of milliseconds to wait until pending requests complete
* @throws AxisFault if the shutdown fails
*/
public void maintenanceShutdown(long milliSecs) throws AxisFault {
if (state != BaseConstants.STARTED) return;
try {
long start = System.currentTimeMillis();
passThroughListeningIOReactorManager.pauseIOReactor(operatingPort);
passThroughListeningIOReactorManager.shutdownIOReactor(operatingPort, milliSecs);
state = BaseConstants.STOPPED;
serviceTracker.stop();
log.info("Listener shutdown in : " + (System.currentTimeMillis() - start) / 1000 + "s");
} catch (IOException e) {
handleException("Error shutting down the IOReactor for maintenance", e);
}
}
/**
* An AxisObserver which will start listening for newly deployed or started services,
* and stop listening when services are un-deployed or stopped.
*/
private class GenericAxisObserver implements AxisObserver {
public void init(AxisConfiguration axisConfig) {}
public void serviceUpdate(AxisEvent event, AxisService service) {
if (!ignoreService(service)
&& BaseUtils.isUsingTransport(service,
sourceConfiguration.getInDescription().getName())) {
switch (event.getEventType()) {
case AxisEvent.SERVICE_DEPLOY :
addToServiceURIMap(service);
System.out.println("SERVICE_DEPLOY");
break;
case AxisEvent.SERVICE_REMOVE :
removeServiceFfromURIMap(service);
System.out.println("SERVICE_REMOVE");
break;
case AxisEvent.SERVICE_START :
addToServiceURIMap(service);
System.out.println("SERVICE_START");
break;
case AxisEvent.SERVICE_STOP :
removeServiceFfromURIMap(service);
System.out.println("SERVICE_STOP");
break;
}
}
}
public void moduleUpdate(AxisEvent event, AxisModule module) {}
public void addParameter(Parameter parameter) throws AxisFault {}
public void removeParameter(Parameter parameter) throws AxisFault {}
public void deserializeParameters(OMElement parameterElement) throws AxisFault {}
public Parameter getParameter(String name) { return null; }
public ArrayList<Parameter> getParameters() { return null; }
public boolean isParameterLocked(String parameterName) { return false; }
public void serviceGroupUpdate(AxisEvent event, AxisServiceGroup serviceGroup) {}
}
private boolean ignoreService(AxisService service) {
// these are "private" services
return service.getName().startsWith("__") || JavaUtils.isTrueExplicitly(
service.getParameter(PassThroughConstants.HIDDEN_SERVICE_PARAM_NAME));
}
private void addToServiceURIMap(AxisService service) {
Parameter param = service.getParameter(PassThroughConstants.SERVICE_URI_LOCATION);
if (param != null) {
String uriLocation = param.getValue().toString();
if (uriLocation.startsWith("/")) {
uriLocation = uriLocation.substring(1);
}
serviceNameToEPRMap.put(service.getName(), uriLocation);
eprToServiceNameMap.put(uriLocation, service.getName());
}
}
private void removeServiceFfromURIMap(AxisService service) {
eprToServiceNameMap.remove(serviceNameToEPRMap.get(service.getName()));
serviceNameToEPRMap.remove(service.getName());
}
private boolean isServiceWithCustomURI(String serviceName){
if(serviceNameToEPRMap.containsKey(serviceName)){
return true;
}
//if map is not containing service name and there is no dot in service name
//service is not have a custom URI
if(!serviceName.contains(".")){
return false;
}
serviceName = serviceName.substring(0,serviceName.lastIndexOf("."));
return isServiceWithCustomURI(serviceName);
}
/** Only call this service IFF isServiceWithCustomURI(serviceName) is true
* Else some nasty things can happen
* @param serviceName
* @return serviceName without endpoint part
*/
private String getServiceNameFromServiceWithCustomURI(String serviceName){
if(serviceNameToEPRMap.containsKey(serviceName)){
return serviceName;
}
serviceName = serviceName.substring(0,serviceName.lastIndexOf("."));
return getServiceNameFromServiceWithCustomURI(serviceName);
}
public String getTransportName() {
return pttInDescription.getName();
}
/**
* Reload SSL configurations from configurations and reset all connections
*
* @param transportInDescription TransportInDescription of the configuration
* @throws AxisFault
*/
public void reloadDynamicSSLConfig(TransportInDescription transportInDescription)
throws AxisFault {
log.info("PassThroughHttpListener reloading SSL Config..");
Parameter oldParameter = transportInDescription.getParameter("SSLProfiles");
Parameter profilePathParam = transportInDescription.getParameter("dynamicSSLProfilesConfig");
if (oldParameter != null && profilePathParam != null) {
transportInDescription.removeParameter(oldParameter);
this.reloadSpecificEndPoints(transportInDescription);
}
}
}