/*
* Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you 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.wso2.carbon.inbound.endpoint.protocol.http.management;
import org.apache.log4j.Logger;
import org.apache.synapse.SynapseException;
import org.apache.synapse.inbound.InboundProcessorParams;
import org.apache.synapse.transport.passthru.SourceHandler;
import org.apache.synapse.transport.passthru.api.PassThroughInboundEndpointHandler;
import org.apache.synapse.transport.passthru.config.SourceConfiguration;
import org.apache.synapse.transport.passthru.core.ssl.SSLConfiguration;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.inbound.endpoint.common.AbstractInboundEndpointManager;
import org.wso2.carbon.inbound.endpoint.persistence.InboundEndpointInfoDTO;
import org.wso2.carbon.inbound.endpoint.protocol.http.InboundHttpConfiguration;
import org.wso2.carbon.inbound.endpoint.protocol.http.InboundHttpConstants;
import org.wso2.carbon.inbound.endpoint.protocol.http.InboundHttpSourceHandler;
import org.wso2.carbon.inbound.endpoint.protocol.http.config.WorkerPoolConfiguration;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/**
* Manager which handles Http Listeners activities for Inbound Endpoints, coordinating
* with Pass-through APIs and registry etc. This is the central place to mange Http Listeners
* for Inbound endpoints
*/
public class HTTPEndpointManager extends AbstractInboundEndpointManager {
private static HTTPEndpointManager instance = new HTTPEndpointManager();
private static final Logger log = Logger.getLogger(HTTPEndpointManager.class);
private ConcurrentHashMap<String,ConcurrentHashMap<Integer, WorkerPoolConfiguration>> workerPoolMap =
new ConcurrentHashMap<String, ConcurrentHashMap<Integer, WorkerPoolConfiguration>>();
private ConcurrentHashMap<String, ConcurrentHashMap<Integer, Pattern>> dispatchPatternMap =
new ConcurrentHashMap<String, ConcurrentHashMap<Integer, Pattern>>();
private HTTPEndpointManager() {
super();
}
public static HTTPEndpointManager getInstance() {
return instance;
}
/**
* Start Http Inbound endpoint in a particular port
* @param port port
* @param name endpoint name
* @param params inbound endpoint params
*/
public boolean startEndpoint(int port, String name, InboundProcessorParams params) {
PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext();
String tenantDomain = carbonContext.getTenantDomain();
InboundHttpConfiguration config = buildConfiguration(port, name, params);
String epName = dataStore.getListeningEndpointName(port, tenantDomain);
if (epName != null) {
if (epName.equalsIgnoreCase(name)) {
applyConfiguration(config, tenantDomain, port);
log.info(epName + " Endpoint is already started in port : " + port);
} else {
String msg = "Another endpoint named : " + epName + " is currently using this port: " + port;
log.warn(msg);
throw new SynapseException(msg);
}
} else {
dataStore.registerListeningEndpoint(port, tenantDomain, InboundHttpConstants.HTTP, name, params);
boolean start = startListener(port, name, params);
if (start) {
applyConfiguration(config, tenantDomain, port);
} else {
dataStore.unregisterListeningEndpoint(port, tenantDomain);
return false;
}
}
return true;
}
/**
* Start Https Inbound endpoint in a particular port
* @param port port
* @param name endpoint name
*/
public boolean startSSLEndpoint(int port , String name, SSLConfiguration sslConfiguration, InboundProcessorParams params) {
PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext();
String tenantDomain = carbonContext.getTenantDomain();
InboundHttpConfiguration config = buildConfiguration(port, name, params);
String epName = dataStore.getListeningEndpointName(port, tenantDomain);
if (PassThroughInboundEndpointHandler.isEndpointRunning(port)) {
if(epName != null && epName.equalsIgnoreCase(name) ){
applyConfiguration(config, tenantDomain, port);
log.info(epName + " Endpoint is already started in port : " + port);
}else{
String msg = "Cannot Start Endpoint "+ name+ " Already occupied port " + port + " by another Endpoint ";
log.warn(msg);
throw new SynapseException(msg);
}
} else {
if(epName != null && epName.equalsIgnoreCase(name)){
log.info(epName + " Endpoint is already registered in registry");
}else{
dataStore.registerSSLListeningEndpoint(port, tenantDomain, InboundHttpConstants.HTTPS, name,
sslConfiguration, params);
}
boolean start = startSSLListener(port, name, sslConfiguration, params);
if (start) {
applyConfiguration(config, tenantDomain, port);
} else {
dataStore.unregisterListeningEndpoint(port, tenantDomain);
return false;
}
}
return true;
}
/**
* Applies worker pool and dispatch patterns to respective maps. This is to be called when a new endpoint is added
* regardless of whether listener (i.e. port) in use is the same.
* @param config
* @param tenantDomain
* @param port
*/
private void applyConfiguration(InboundHttpConfiguration config, String tenantDomain, int port) {
if (config.getCoresize() != null && config.getMaxSize() != null && config.getKeepAlive() != null
&& config.getQueueLength() != null) {
WorkerPoolConfiguration workerPoolConfiguration = new WorkerPoolConfiguration(
config.getCoresize(),
config.getMaxSize(),
config.getKeepAlive(),
config.getQueueLength(),
config.getThreadGroup(),
config.getThreadID());
addWorkerPool(tenantDomain, port, workerPoolConfiguration);
}
if (config.getDispatchPattern() != null) {
Pattern pattern = compilePattern(config.getDispatchPattern());
addDispatchPattern(tenantDomain, port, pattern);
}
}
/**
* Start Http Listener in a particular port
* @param port port
* @param name endpoint name
* @param params inbound endpoint params
*/
public boolean startListener(int port, String name, InboundProcessorParams params) {
if (PassThroughInboundEndpointHandler.isEndpointRunning(port)) {
log.info("Listener is already started for port : " + port);
return true;
}
SourceConfiguration sourceConfiguration = null;
try {
sourceConfiguration = PassThroughInboundEndpointHandler.getPassThroughSourceConfiguration();
} catch (Exception e) {
log.warn("Cannot get PassThroughSourceConfiguration ", e);
return false;
}
if (sourceConfiguration != null) {
//Create Handler for handle Http Requests
SourceHandler inboundSourceHandler = new InboundHttpSourceHandler(port, sourceConfiguration);
try {
//Start Endpoint in given port
PassThroughInboundEndpointHandler.startEndpoint(new InetSocketAddress(port),
inboundSourceHandler, name);
} catch (NumberFormatException e) {
log.error("Exception occurred while starting listener for endpoint : "
+ name + " ,port " + port, e);
}
} else {
log.warn("SourceConfiguration is not registered in PassThrough Transport hence not start inbound endpoint " + name);
return false;
}
return true;
}
/**
* Start Http Listener in a particular port
* @param port port
* @param name endpoint name
*/
public boolean startSSLListener(int port, String name, SSLConfiguration sslConfiguration, InboundProcessorParams params) {
if (PassThroughInboundEndpointHandler.isEndpointRunning(port)) {
log.info("Listener is already started for port : " + port);
return true;
}
SourceConfiguration sourceConfiguration = null;
try {
sourceConfiguration = PassThroughInboundEndpointHandler.getPassThroughSSLSourceConfiguration();
} catch (Exception e) {
log.warn("Cannot get PassThroughSSLSourceConfiguration ", e);
return false;
}
if (sourceConfiguration != null) {
//Create Handler for handle Http Requests
SourceHandler inboundSourceHandler = new InboundHttpSourceHandler(port, sourceConfiguration);
try {
//Start Endpoint in given port
PassThroughInboundEndpointHandler.startSSLEndpoint(new InetSocketAddress(port),
inboundSourceHandler, name, sslConfiguration);
} catch (NumberFormatException e) {
log.error("Exception occurred while starting listener for endpoint : "
+ name + " ,port " + port, e);
return false;
}
} else {
log.warn("SourceConfiguration is not registered in PassThrough Transport hence not start inbound endpoint " + name);
return false;
}
return true;
}
/**
* Stop Inbound Endpoint
* @param port port of the endpoint
*/
public void closeEndpoint(int port) {
PrivilegedCarbonContext cc = PrivilegedCarbonContext.getThreadLocalCarbonContext();
String tenantDomain = cc.getTenantDomain();
dataStore.unregisterListeningEndpoint(port, tenantDomain);
removeWorkerPoolConfiguration(tenantDomain, port);
removeDispatchPattern(tenantDomain, port);
if (!PassThroughInboundEndpointHandler.isEndpointRunning(port)) {
log.info("Listener Endpoint is not started");
return ;
} else if (dataStore.isEndpointRegistryEmpty(port)) {
// if no other endpoint is working on this port. close the http listening endpoint
PassThroughInboundEndpointHandler.closeEndpoint(port);
}
}
public InboundHttpConfiguration buildConfiguration(int port, String name, InboundProcessorParams params) {
return new InboundHttpConfiguration.InboundHttpConfigurationBuilder(port, name)
.workerPoolCoreSize(params.getProperties().getProperty(
InboundHttpConstants.INBOUND_WORKER_POOL_SIZE_CORE))
.workerPoolMaxSize(params.getProperties().getProperty(
InboundHttpConstants.INBOUND_WORKER_POOL_SIZE_MAX))
.workerPoolKeepAlive(params.getProperties().getProperty(
InboundHttpConstants.INBOUND_WORKER_THREAD_KEEP_ALIVE_SEC))
.workerPoolQueueLength(params.getProperties().getProperty(
InboundHttpConstants.INBOUND_WORKER_POOL_QUEUE_LENGTH))
.workerPoolThreadGroup(params.getProperties().getProperty(
InboundHttpConstants.INBOUND_THREAD_GROUP_ID))
.workerPoolThreadId(params.getProperties().getProperty(
InboundHttpConstants.INBOUND_THREAD_ID))
.dispatchPattern(params.getProperties().getProperty(
InboundHttpConstants.INBOUND_ENDPOINT_PARAMETER_DISPATCH_FILTER_PATTERN))
.build();
}
/**
* Start Http listeners for all the Inbound Endpoints. This should be called in the
* server startup to load all the required listeners for endpoints in all tenants
*/
public void loadEndpointListeners() {
Map<Integer, List<InboundEndpointInfoDTO>> tenantData = dataStore.getAllListeningEndpointData();
for (Map.Entry tenantInfoEntry : tenantData.entrySet()) {
int port = (Integer) tenantInfoEntry.getKey();
InboundEndpointInfoDTO inboundEndpointInfoDTO =
(InboundEndpointInfoDTO) ((ArrayList) tenantInfoEntry.getValue()).get(0);
if (inboundEndpointInfoDTO.getProtocol().equals(InboundHttpConstants.HTTP)) {
startListener(port, inboundEndpointInfoDTO.getEndpointName(), inboundEndpointInfoDTO.getInboundParams());
} else if (inboundEndpointInfoDTO.getProtocol().equals(InboundHttpConstants.HTTPS)) {
startSSLListener(port, inboundEndpointInfoDTO.getEndpointName(), inboundEndpointInfoDTO.getSslConfiguration(),
inboundEndpointInfoDTO.getInboundParams());
}
}
}
/**
*Method for add worker pool configs
* @param tenantDomain
* @param port
* @param workerPoolConfiguration
*/
public void addWorkerPool(String tenantDomain,int port ,WorkerPoolConfiguration workerPoolConfiguration){
ConcurrentHashMap concurrentHashMap = workerPoolMap.get(tenantDomain);
if(concurrentHashMap == null){
concurrentHashMap = new ConcurrentHashMap<Integer, WorkerPoolConfiguration>();
concurrentHashMap.put(port,workerPoolConfiguration);
workerPoolMap.put(tenantDomain,concurrentHashMap);
} else {
concurrentHashMap.put(port,workerPoolConfiguration);
}
}
/**
* Method for get WorkerPool Config
* @param tenantDomain
* @param port
* @return
*/
public WorkerPoolConfiguration getWorkerPoolConfiguration(String tenantDomain, int port){
ConcurrentHashMap concurrentHashMap = workerPoolMap.get(tenantDomain);
if(concurrentHashMap != null){
Object val = concurrentHashMap.get(port);
if(val instanceof WorkerPoolConfiguration){
return (WorkerPoolConfiguration)val;
}
}
return null;
}
/**
* Remove Worker Pool
* @param tenantDomian Tenant Domain
* @param port Port
*/
public void removeWorkerPoolConfiguration(String tenantDomian, int port ){
ConcurrentHashMap concurrentHashMap = workerPoolMap.get(tenantDomian);
if(concurrentHashMap != null){
if(concurrentHashMap.containsKey(port)){
concurrentHashMap.remove(port);
}
}
}
/**
* Adds a dispatch pattern to pattern map.
* @param tenantDomain
* @param port
*/
public void addDispatchPattern(String tenantDomain, int port, Pattern pattern) {
ConcurrentHashMap concurrentHashMap = dispatchPatternMap.get(tenantDomain);
if(concurrentHashMap == null){
concurrentHashMap = new ConcurrentHashMap<Integer, Pattern>();
concurrentHashMap.put(port, pattern);
dispatchPatternMap.put(tenantDomain, concurrentHashMap);
} else {
concurrentHashMap.put(port, pattern);
}
}
/**
* Removes a dispatch pattern from pattern map.
* @param tenantDomain
* @param port
*/
public void removeDispatchPattern(String tenantDomain, int port) {
ConcurrentHashMap concurrentHashMap = dispatchPatternMap.get(tenantDomain);
if(concurrentHashMap != null){
if(concurrentHashMap.containsKey(port)){
concurrentHashMap.remove(port);
}
}
}
/**
* Method to get pattern for tenant and port.
* @param tenantDomain
* @param port
* @return
*/
public Pattern getPattern(String tenantDomain, int port){
ConcurrentHashMap concurrentHashMap = dispatchPatternMap.get(tenantDomain);
if(concurrentHashMap != null){
Object val = concurrentHashMap.get(port);
if(val instanceof Pattern){
return (Pattern) val;
}
}
return null;
}
protected Pattern compilePattern(String dispatchPattern) {
try {
return Pattern.compile(dispatchPattern, Pattern.COMMENTS | Pattern.DOTALL);
} catch (PatternSyntaxException e) {
log.error("Dispatch pattern " + dispatchPattern + " is an invalid pattern.");
throw new SynapseException(e);
}
}
}