/*
* Copyright WSO2, Inc. (http://wso2.com)
*
* 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.wso2.carbon.cloud.gateway.service;
import java.net.SocketException;
import org.apache.axis2.description.TransportOutDescription;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.axis2.engine.AxisEvent;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.SynapseConstants;
import org.wso2.carbon.cloud.gateway.common.*;
import org.wso2.carbon.cloud.gateway.transport.CGTransportSender;
import org.wso2.carbon.mediation.initializer.AbstractServiceBusAdmin;
import org.wso2.carbon.proxyadmin.ProxyAdminException;
import org.wso2.carbon.proxyadmin.ProxyData;
import org.wso2.carbon.proxyadmin.Entry;
import org.wso2.carbon.proxyadmin.service.ProxyServiceAdmin;
import org.wso2.carbon.registry.api.RegistryException;
import org.wso2.carbon.registry.api.Resource;
import org.wso2.carbon.registry.core.Registry;
import org.wso2.carbon.registry.core.RegistryConstants;
import org.wso2.carbon.registry.core.jdbc.utils.Transaction;
import org.wso2.carbon.service.mgt.ServiceAdmin;
import org.wso2.carbon.service.mgt.ServiceMetaData;
/**
* The class <code>CGAdminService</code> service provides the operations for deploying the proxies
* for out sliders. These proxies are the actual proxies that represent the internal
*/
public class CGAdminService extends AbstractServiceBusAdmin {
private static final Log log = LogFactory.getLog(CGAdminService.class);
/**
* Deploy the proxy service
*
* @param metaData meta data associated with this proxy
* @throws org.wso2.carbon.cloud.gateway.common.CGException throws in case of an error
*/
public void deployProxy(CGServiceMetaDataBean metaData) throws CGException {
if (metaData == null) {
handleException("CG Service meta data is null");
}
try {
ProxyData proxyData = createProxyData(metaData);
new ProxyServiceAdmin().addProxy(proxyData);
// enable CG transport sender for this tenant if not already done so,
// this has to done this way because Stratos has no mechanism to enable custom
// transport senders, because all messages are supposed to go through ST out flow
// we can get rid of this once Stratos has that capabilities
if (getAxisConfig().getTransportOut(CGConstant.CG_TRANSPORT_NAME) == null) {
enableCGTransportSender(getAxisConfig());
}
} catch (Exception e) {
handleException("Could not deploy the CG service '" + metaData.getServiceName() + "'. "
+ e.getMessage(), e);
}
}
/**
* Un deploy the proxy service
*
* @param serviceName the name of the proxy to un deploy
* @throws org.wso2.carbon.cloud.gateway.common.CGException throws in case of an error
*/
public void unDeployProxy(String serviceName) throws CGException {
if (serviceName == null) {
handleException("CG service(proxy service) name is null");
}
try {
new ProxyServiceAdmin().deleteProxyService(serviceName);
deleteWSDLResources(serviceName);// remove the wsdl document from the registry
} catch (ProxyAdminException e) {
handleException("Could not delete the CG service '" + serviceName + "'. "
+ e.getMessage(), e);
}
}
/**
* Returns the thrift server connection param
*
* @return the thriftServer connection url
* @throws org.wso2.carbon.cloud.gateway.common.CGException In case the server is not running
*/
public CGThriftServerBean getThriftServerConnectionBean() throws CGException {
CGThriftServerBean bean;
try {
String hostName = CGUtils.getCGThriftServerHostName();
int port = CGUtils.getCGThriftServerPort();
int timeOut = CGUtils.getIntProperty(CGConstant.CG_THRIFT_CLIENT_TIMEOUT,
CGConstant.DEFAULT_TIMEOUT);
if (!CGUtils.isServerAlive(hostName, port)) {
handleException("Thrift server is not running on the host '" + hostName + "'" +
" in port '" + port + "'");
}
bean = new CGThriftServerBean();
bean.setHostName(hostName);
bean.setPort(port);
bean.setTimeOut(timeOut);
} catch (SocketException e) {
throw new CGException(e);
}
return bean;
}
/**
* Update the public proxy based on the new event of the back end service
*
* @param serviceName service
* @param eventType the new event type
* @throws org.wso2.carbon.cloud.gateway.common.CGException throws in case of an error
*/
public void updateProxy(String serviceName, int eventType) throws CGException {
ProxyServiceAdmin proxyAdmin = new ProxyServiceAdmin();
try {
if (eventType == AxisEvent.SERVICE_REMOVE) {
proxyAdmin.deleteProxyService(serviceName);
} else if (eventType == AxisEvent.SERVICE_START) {
proxyAdmin.startProxyService(serviceName);
} else if (eventType == AxisEvent.SERVICE_STOP) {
proxyAdmin.stopProxyService(serviceName);
} else {
if (log.isDebugEnabled()) {
log.debug("The event SERVICE_DEPLOY is supported for the service '" + serviceName + "'");
}
}
} catch (ProxyAdminException e) {
handleException("Could not update service proxy service '" + serviceName + "'", e);
}
}
public CGProxyToolsURLs getPublishedProxyToolsURLs(String serviceName, String domainName)
throws CGException {
try {
CGProxyToolsURLs tools = new CGProxyToolsURLs();
ServiceMetaData data = new ServiceAdmin().getServiceData(serviceName);
tools.setTryItURL(data.getTryitURL());
tools.setWsdl11URL(data.getWsdlURLs()[0]);
tools.setWsdl2URL(data.getWsdlURLs()[1]);
tools.setEprArray(data.getEprs());
return tools;
} catch (Exception e) {
handleException("Could not read the proxy service URL for the service '" + serviceName
+ "', for the domain '" + domainName + "'", e);
}
return null;
}
private ProxyData createProxyData(CGServiceMetaDataBean proxyMetaData) throws CGException {
if (log.isDebugEnabled()) {
log.debug("Creating the proxy data with following metadata");
log.debug("Service Name : " + proxyMetaData.getServiceName());
log.debug("CGTransport endpoint : " + proxyMetaData.getEndpoint());
log.debug("Has In Out operations? : " + proxyMetaData.isHasInOutMEP());
log.debug("Enable MTOM? : " + proxyMetaData.isMTOMEnabled());
log.debug("Has WS-Sec enabled ? : " + proxyMetaData.isWsSecEnabled());
log.debug("Has WS-RM enabled ? : " + proxyMetaData.isWsRmEnabled());
if (proxyMetaData.isWsSecEnabled()) {
log.debug("WS-Sec policy : \n" + proxyMetaData.getSecPolicy());
}
if (proxyMetaData.isWsRmEnabled()) {
log.debug("WS-RM policy : \n" + proxyMetaData.getRmPolicy());
}
log.debug("WSDL location : " + proxyMetaData.getWsdlLocation());
log.debug("WSDL : \n" + proxyMetaData.getInLineWSDL());
if (proxyMetaData.getServiceDependencies() != null &&
proxyMetaData.getServiceDependencies().length > 0) {
for (CGServiceDependencyBean dependencyBean : proxyMetaData.getServiceDependencies()) {
log.debug("Dependency Key : " + dependencyBean.getKey());
log.debug("Dependency Content : \n" + dependencyBean.getContent());
}
}
}
ProxyData proxy = new ProxyData();
proxy.setName(proxyMetaData.getServiceName());
if (proxyMetaData.getInLineWSDL() != null) {
String persistedWsdlPath = persistWSDL(proxyMetaData.getServiceName(),
proxyMetaData.getInLineWSDL(), proxyMetaData.getServiceDependencies());
proxy.setWsdlKey(persistedWsdlPath);
if (!ArrayUtils.isEmpty(proxyMetaData.getServiceDependencies())) {
CGServiceDependencyBean[] dependencies = proxyMetaData.getServiceDependencies();
Entry[] resourceEntries = new Entry[proxyMetaData.getServiceDependencies().length];
for (int i = 0; i < resourceEntries.length; i++) {
resourceEntries[i] =
new Entry(
dependencies[i].getKey(),
composeServiceResourcesPath(proxyMetaData.getServiceName()) +
dependencies[i].getKey());
}
proxy.setWsdlResources(resourceEntries);
}
}
// FIXME - this is the workaround for https://issues.apache.org/jira/browse/SYNAPSE-527 and
// https://issues.apache.org/jira/browse/AXIS2-4196
String inSeq =
"<inSequence xmlns=\"" + SynapseConstants.SYNAPSE_NAMESPACE + "\">" +
"<class name=\"org.wso2.carbon.cloud.gateway.CGMEPHandlingMediator\"/>" +
"<property name=\"transportNonBlocking\" scope=\"axis2\" action=\"remove\"/>" +
"<property name=\"preserveProcessedHeaders\" value=\"true\"/>";
if (proxyMetaData.isHasInOutMEP()) {
proxy.setOutSeqXML(
"<outSequence xmlns=\"" + SynapseConstants.SYNAPSE_NAMESPACE + "\">" +
"<send/>" +
"</outSequence>");
} else {
inSeq = inSeq + "<property name=\"OUT_ONLY\" scope=\"axis2\" action=\"set\" value=\"true\"/>";
}
inSeq = inSeq + "</inSequence>";
proxy.setInSeqXML(inSeq);
proxy.setFaultSeqXML(
"<faultSequence xmlns=\"" + SynapseConstants.SYNAPSE_NAMESPACE + "\">" +
"<log level=\"full\"/>" +
"<drop/>" +
"</faultSequence>");
// add a dummy error code in order to make sure that we don't suspend the endpoint
// for any type of error, the proxy is just a middle man and it has an in memory endpoint
String endpointXML =
"<endpoint xmlns=\"" + SynapseConstants.SYNAPSE_NAMESPACE + "\">" +
"<address uri=\"" + proxyMetaData.getEndpoint() + "\">" +
"<suspendOnFailure>" +
"<errorCodes>400207</errorCodes>" +
"<initialDuration>1000</initialDuration>" +
"<progressionFactor>2</progressionFactor>" +
"<maximumDuration>64000</maximumDuration>" +
"</suspendOnFailure>" +
"</address>" +
"</endpoint>";
proxy.setEndpointXML(endpointXML);
return proxy;
}
private void handleException(String msg) throws CGException {
log.error(msg);
throw new CGException(msg);
}
private void handleException(String msg, Throwable t) throws CGException {
log.error(msg, t);
throw new CGException(msg, t);
}
private void enableCGTransportSender(AxisConfiguration axisConfig)
throws Exception {
CGTransportSender sender = new CGTransportSender();
TransportOutDescription transportOut =
new TransportOutDescription(CGConstant.CG_TRANSPORT_NAME);
transportOut.setSender(sender);
axisConfig.addTransportOut(transportOut);
transportOut.getSender().init(getConfigContext(), transportOut);
}
/**
* Saves the Wsdl of service published onto this server in Governance user
* registry
*
* @param serviceName Name of the service
* @param wsdl content of the Wsdl document.
* @return returns the key of the stored WSDL
* @throws org.wso2.carbon.cloud.gateway.common.CGException in case of an error.
*/
private String persistWSDL(String serviceName, String wsdl, CGServiceDependencyBean[] dependencies) throws CGException {
boolean isTransactionAlreadyStarted = Transaction.isStarted();
boolean isTransactionSuccess = true;
Registry registry = getGovernanceUserRegistry();
String serviceResourcesPath = composeServiceResourcesPath(serviceName);
String wsdlPath = serviceResourcesPath + serviceName + ".wsdl";
try {
if (!isTransactionAlreadyStarted) {
registry.beginTransaction(); // start a transaction if none
// exists currently.
}
if (registry.resourceExists(serviceResourcesPath)) {
// delete the resource and add it again
if (log.isDebugEnabled()) {
log.debug("Replacing the Wsdl for the service: " + serviceName);
}
registry.delete(serviceResourcesPath);
}
Resource resource = registry.newResource();
resource.setContent(wsdl);
if (log.isDebugEnabled()) {
log.debug("Adding wsdl to registry. Service name: " + serviceName);
}
registry.put(wsdlPath , resource);
if (!ArrayUtils.isEmpty(dependencies)) {
for (CGServiceDependencyBean dependency : dependencies) {
Resource dependencyResource = registry.newResource();
dependencyResource.setContent(dependency.getContent());
registry.put(serviceResourcesPath + dependency.getKey(), dependencyResource);
}
}
} catch (RegistryException e) {
isTransactionSuccess = false;
handleException("Error occurred while saving the wsdl into registry", e);
} finally {
if (!isTransactionAlreadyStarted) {
try {
if (isTransactionSuccess) {
// commit the transaction since we started it.
registry.commitTransaction();
} else {
registry.rollbackTransaction();
}
} catch (RegistryException re) {
handleException("Error occurred while trying to rollback or commit the " +
"transaction", re);
}
}
}
return wsdlPath;
}
/**
* Deletes the Wsdl of service published onto this server
*
* @param serviceName Name of the service
* @throws org.wso2.carbon.cloud.gateway.common.CGException in case of an error.
*/
private void deleteWSDLResources(String serviceName) throws CGException {
boolean isTransactionAlreadyStarted = Transaction.isStarted();
boolean isTransactionSuccess = true;
Registry registry = getGovernanceUserRegistry();
String resourcePath = composeServiceResourcesPath(serviceName);
try {
if (!isTransactionAlreadyStarted) {
// start a transaction if none exists currently.
registry.beginTransaction();
}
if (registry.resourceExists(resourcePath)) {
// delete the resource ( - Wsdl document)
registry.delete(resourcePath);
}
} catch (RegistryException e) {
isTransactionSuccess = false;
handleException("Error occurred while deleting the wsdl from registry", e);
} finally {
if (!isTransactionAlreadyStarted) {
try {
if (isTransactionSuccess) {
// commit the transaction since we started it.
registry.commitTransaction();
} else {
registry.rollbackTransaction();
}
} catch (RegistryException re) {
handleException("Error occurred while trying to rollback or " +
"commit the transaction", re);
}
}
}
}
/**
* Constructs the registry path to persist a wsdl document ( - belonging to
* service published on CG server)
*
* @param serviceName Name of the service
* @return Designated path in registry
*/
public String composeServiceResourcesPath(String serviceName) {
return new StringBuilder().append(CGConstant.REGISTRY_CG_WSDL_RESOURCE_PATH)
.append(RegistryConstants.PATH_SEPARATOR).append(serviceName)
.append(RegistryConstants.PATH_SEPARATOR).toString();
}
}