/*
* Copyright 2009-2010 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.csg.agent.jms;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMNode;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.axis2.description.AxisOperation;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.axis2.wsdl.WSDLConstants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.cloud.csg.agent.CSGAgentUtils;
import org.wso2.carbon.cloud.csg.agent.CSGServicePublisher;
import org.wso2.carbon.cloud.csg.agent.client.CSGAdminClient;
import org.wso2.carbon.cloud.csg.common.CSGCommonUtils;
import org.wso2.carbon.cloud.csg.common.CSGConstant;
import org.wso2.carbon.cloud.csg.common.CSGException;
import org.wso2.carbon.cloud.csg.common.CSGServerBean;
import org.wso2.carbon.cloud.csg.stub.types.common.ServiceMetaData;
import org.wso2.carbon.core.AbstractAdmin;
import org.wso2.carbon.core.multitenancy.SuperTenantCarbonContext;
import org.wso2.carbon.core.transports.util.TransportParameter;
import org.wso2.carbon.core.transports.util.TransportSummary;
import org.wso2.carbon.core.util.CryptoException;
import org.wso2.carbon.core.util.CryptoUtil;
import org.wso2.carbon.registry.core.Collection;
import org.wso2.carbon.registry.core.Resource;
import org.wso2.carbon.registry.core.exceptions.RegistryException;
import org.wso2.carbon.service.mgt.ServiceAdmin;
import org.wso2.carbon.transport.jms.JMSTransportAdmin;
import org.wso2.carbon.transport.mgt.TransportAdmin;
import org.wso2.carbon.utils.CarbonUtils;
import java.io.File;
import java.util.Iterator;
import java.util.Properties;
/**
* The class <code>JMSServicePublisher</code> provides the API for exposing a service in JMS transport.
* The JMS model work as follows. Upon user say publish on a private service, that service will be
* exposed on JMS, at the same time a JMS proxy will be created which will actually communicate
* with the out side. If the service is in/out MEP them service will be creating a second reply
* JMS queue and the JMS proxy will be a two way JMS proxy
*/
public class JMSServicePublisher extends AbstractAdmin implements CSGServicePublisher {
private static final Log log = LogFactory.getLog(JMSServicePublisher.class);
/**
* The running axis2 configuration
*/
private AxisConfiguration axis2Conf;
/**
* The registry instance
*/
private org.wso2.carbon.registry.core.Registry registry;
private JMSTransportAdmin jmsAdmin;
private TransportAdmin transportAdmin;
public JMSServicePublisher() {
axis2Conf = getAxisConfig();
registry = getConfigSystemRegistry();
jmsAdmin = new JMSTransportAdmin();
transportAdmin = new TransportAdmin();
}
/**
* Expose the private service in public JMS. The method is synchronized to make sure that
* no two threads will be trying to deploy the same service.
*
* @param serviceName name of the service to be exposed in JMS
* @return does the service published successfully ?
* @throws CSGException throws in case of an error
*/
public synchronized boolean publish(String serviceName, String serverName)
throws CSGException {
if (serviceName == null) {
log.error("The service name is null");
return false;
}
try {
CSGServerBean csgServer = getCSGServerBean(serverName);
if (csgServer == null) {
throw new CSGException("No persist information found for the server name : " +
serverName);
}
String userName = csgServer.getUserName();
String passWord = csgServer.getPassWord();
String domainName = csgServer.getDomainName();
String sessionCookie = CSGAgentUtils.getSessionCookie(getAuthServiceURL(csgServer),
userName, passWord, domainName, csgServer.getHost());
CSGAdminClient csgAdminClient;
if (CSGAgentUtils.isClientAxis2XMLExists()) {
ConfigurationContext configCtx = ConfigurationContextFactory.
createConfigurationContextFromFileSystem(null, CSGConstant.CLIENT_AXIS2_XML);
csgAdminClient = new CSGAdminClient(sessionCookie, getProxyURL(csgServer), configCtx);
} else {
csgAdminClient = new CSGAdminClient(sessionCookie, getProxyURL(csgServer));
}
boolean isExposedOnJMS = isExposedOnJMS(serviceName);
ServiceAdmin serverAdmin = new ServiceAdmin(axis2Conf);
serverAdmin.addTransportBinding(serviceName, CSGConstant.JMS_PREFIX);
AxisService service = axis2Conf.getService(serviceName);
// enable JMS transport sender if required
// Note: we need to enable JMS sender before JMS listener otherwise
// AbstractTransportListener.java:96 ( where we initialize transportOut) will be initialized
// to a null value which causes the response to be lost and JMSSender at client side is
// end up with a warning for not receiving the response
if(hasInOutOperations(service)){
enableJMSTransportSender(serviceName);
}
// enable JMS listener for this service
enableJMSTransportListner(serviceName, csgAdminClient, userName, passWord,
csgServer.getHost(), csgServer.getDomainName());
// deploy the proxy on CSG server
csgAdminClient.deployProxy(getServiceMetaData(service, domainName));
// flag the service as published
flagServicePublishing(serviceName, serverName, true, isExposedOnJMS);
} catch (Exception e) {
handleException("Cloud not publish service '" + serviceName + "'", e);
}
return true;
}
/**
* Un publish the service from public
* @param serviceName service to unpublished
* @return does the service unpublished successfully?
* @throws CSGException throws in case of an error
*/
public synchronized boolean unPublish(String serviceName, String serverName)
throws CSGException {
if (serviceName == null) {
log.error("The service name is null");
return false;
}
AxisService service = axis2Conf.getServiceForActivation(serviceName);
if (service == null) {
handleException("No service found with the name '" + serviceName + "'");
} else {
try {
boolean isJMSEnabled = true;
String flagResourcePath =
CSGConstant.REGISTRY_FLAG_RESOURCE_PATH + "/" + serviceName + ".flag";
// remove the transport binding only if we add them
if (registry.resourceExists(flagResourcePath)) {
Resource resource = registry.get(flagResourcePath);
if ("false".equals(resource.getProperty(CSGConstant.CSG_IS_JMS_ENABLED))) {
transportAdmin.removeExposedTransports(serviceName, CSGConstant.JMS_PREFIX);
isJMSEnabled = false;
}
}
CSGServerBean csgServer = getCSGServerBean(serverName);
if (csgServer == null) {
throw new CSGException("No CSG server information found with the name : " +
serverName);
}
String sessionCookie = CSGAgentUtils.getSessionCookie(
getAuthServiceURL(csgServer),
csgServer.getUserName(),
csgServer.getPassWord(),
csgServer.getDomainName(),
csgServer.getHost());
CSGAdminClient csgAdminClient;
if (CSGAgentUtils.isClientAxis2XMLExists()) {
ConfigurationContext configCtx = ConfigurationContextFactory.
createConfigurationContextFromFileSystem(null, CSGConstant.CLIENT_AXIS2_XML);
csgAdminClient = new CSGAdminClient(sessionCookie, getProxyURL(csgServer), configCtx);
} else {
csgAdminClient = new CSGAdminClient(sessionCookie, getProxyURL(csgServer));
}
csgAdminClient.unDeployProxy(csgServer.getDomainName() + serviceName + "Proxy");
// flag the service as unpublished
flagServicePublishing(serviceName, serverName, false, isJMSEnabled);
} catch (Exception e) {
handleException("Cloud not remove the JMS transport from the service '" +
serviceName + "'",e);
}
}
return true;
}
private ServiceMetaData getServiceMetaData(AxisService service, String domainName) throws CSGException {
try {
ServiceMetaData privateServiceMetaData = new ServiceMetaData();
privateServiceMetaData.setServiceName(domainName + service.getName() + "Proxy");
ServiceAdmin serviceAdmin = new ServiceAdmin(axis2Conf);
org.wso2.carbon.service.mgt.ServiceMetaData serviceAdminMetaData =
serviceAdmin.getServiceData(service.getName());
String eprs[] = serviceAdminMetaData.getEprs();
boolean isJMSFound = false;
if (eprs != null) {
for (String epr : eprs) {
if (epr != null && epr.contains(CSGConstant.JMS_TRANSPORT_PREFIX)) {
privateServiceMetaData.setEndpoint(epr);
isJMSFound = true;
break;
}
}
} else {
throw new CSGException("Error while configuring the transports!");
}
if (!isJMSFound) {
throw new CSGException("Cloud not determine the JMS epr of the service '" +
service.getName() + "'. This is required for service publishing. " +
"Check if the Qpid broker is running!");
}
if (serviceAdminMetaData.isActive()) {
String wsdlLocation = serviceAdminMetaData.getWsdlURLs()[0];
OMNode wsdNode =
CSGAgentUtils.getOMElementFromURI(wsdlLocation);
OMElement wsdlElement;
if (wsdNode instanceof OMElement) {
wsdlElement = (OMElement) wsdNode;
} else {
throw new CSGException("Invalid instance type detected when parsing the WSDL '"
+ wsdlLocation + "'. Required OMElement type!");
}
privateServiceMetaData.setInLineWSDL(wsdlElement.toStringWithConsume());
}
if(hasInOutOperations(service)){
privateServiceMetaData.setHasInOutMEP(true);
}
return privateServiceMetaData;
} catch (Exception e) {
handleException("Cloud not get the meta Data", e);
}
return null;
}
/**
* Enable the JMS transport listener for this service, this will build up a configuration
* required by Qpid broker component, like the JNDI properties read from a property file
* @param serviceName the service name
* @param csgAdminClient the remote CSG admin client
* @param userName remote CSG server's user name
* @param passWord remote CSG server's password
* @param host the connection url of remote csg server
* @param domain the domain name parameter
* @throws CSGException throws in case of an error
*/
private void enableJMSTransportListner(String serviceName,
CSGAdminClient csgAdminClient,
String userName,
String passWord,
String host,
String domain) throws CSGException {
try {
createOrUpdateQpidJNDIFile(csgAdminClient, serviceName, userName, passWord, domain);
String serviceQueueConFac = serviceName + "QueueConnectionFactory";
StringBuffer stringBuf = new StringBuffer();
stringBuf
.append("<parameter name=\"default\">")
.append("<parameter name=\"java.naming.provider.url\">").append("repository").append(File.separator).append("tenants").append(File.separator).append(domain).append(File.separator)
.append("csg").append(File.separator).append(CSGConstant.QPID_CONFIG_FILE).append("</parameter>")
.append("<parameter name=\"java.naming.factory.initial\">org.apache.qpid.wso2.jndi.TenantAwareInitialContextFactory</parameter>")
.append("<parameter name=\"transport.jms.ConnectionFactoryJNDIName\">QueueConnectionFactory</parameter>")
.append("<parameter name=\"transport.jms.ConnectionFactoryType\">queue</parameter>")
// .append("<parameter name=\"transport.jms.UserName\">").append(userName).append("</parameter>")
// .append("<parameter name=\"transport.jms.Password\">").append(passWord).append("</parameter>")
.append("</parameter>");
TransportParameter[] transportParam = new TransportParameter[1];
transportParam[0] = new TransportParameter();
transportParam[0].setName(serviceQueueConFac);
transportParam[0].setParamElement(stringBuf.toString());
getAxisConfig().getService(serviceName).addParameter(CSGConstant.PARAM_JMS_CONFAC,
serviceName +
"QueueConnectionFactory");
jmsAdmin.updateServiceSpecificInParameters(serviceName, transportParam);
} catch (Exception e) {
handleException("Cloud not enable JMS listener for '" + serviceName + "'", e);
}
}
private void createOrUpdateQpidJNDIFile(
CSGAdminClient csgAdminClient,
String serviceName,
String userName,
String passWord,
String domain) throws CSGException {
try {
String maskedURL = CSGCommonUtils.getMTAwareConnectionURL(userName, passWord, domain,
csgAdminClient.getRemoteConnectionURL());
String carbonTenantDir = CarbonUtils.getCarbonTenantsDirPath();
if (carbonTenantDir != null) {
File qpidConfFile = new File(carbonTenantDir + File.separator + domain +
File.separator + "csg" + File.separator, CSGConstant.QPID_CONFIG_FILE);
String filePath = qpidConfFile.getPath();
String queueJNDIName = serviceName + CSGConstant.CREATE_ALWAYS;
String queueName = "queue." + serviceName;
if (qpidConfFile.exists()) {
// set the JNDI name for the queue
// eg. queue.SimpleStockQuoteService=SimpleStockQuoteService
CSGCommonUtils.updatePropertyFile(qpidConfFile, queueName + "=" + queueJNDIName);
} else {
// fill the new property file with intial content
// connectionfactory.QueueConnectionFactory=amqp://guest:guest@clientid/jkh?brokerlist='tcp://localhost:5672'
// queue.SimpleStockQuoteService=SimpleStockQuoteService
// org.apache.qpid.wso2.default.ConnectionFactoryJNDIName=QueueConnectionFactory
CSGCommonUtils.updatePropertyFile(qpidConfFile,
"connectionfactory.QueueConnectionFactory" + "=" + maskedURL);
CSGCommonUtils.updatePropertyFile(qpidConfFile, queueName + "="
+ queueJNDIName);
CSGCommonUtils.updatePropertyFile(qpidConfFile,
CSGConstant.WSO2_QPID_JNDI_STRING);
}
Properties prop = CSGCommonUtils.loadProperties(filePath);
csgAdminClient.createServerQpidJNDIFile(
getQpidJNDIString(prop, userName, passWord), domain);
} else {
handleException("Carbon Tenant directory could not found, this is required to" +
" store the Qpid JNDI property file");
}
} catch (AxisFault axisFault) {
handleException("Cloud not update the Qpid JNDI file", axisFault);
}
}
private void enableJMSTransportSender(String serviceName) throws CSGException{
try {
jmsAdmin.updateServiceSpecificOutParameters(serviceName,
jmsAdmin.getServiceSpecificOutParameters(serviceName));
log.info("Enabled JMS transport Sender for the service '" + serviceName + "'");
} catch (Exception e) {
handleException("Clound not enable JMSSender for '" + serviceName + "'", e);
}
}
/**
* We'll be receving URL with the following format and replace that with the actual username
* and the password
* @param originalURL the Qpid connection URL
* @param userName username of the user
* @param passWord password of the user
* @return the masked url
*/
private String maskTheURL(String originalURL, String userName, String passWord){
// FIXME- we may need to read the direct connection url given the username
return originalURL.replace(CSGConstant.CSG_AMQP_USER_NAME,userName).
replace(CSGConstant.CSG_AMQP_PASSWORD, passWord);
}
private String getAuthServiceURL(CSGServerBean csgServer){
return "https://" + csgServer.getHost() + ":" + csgServer.getPort() +
"/services/AuthenticationAdmin";
}
private String getProxyURL(CSGServerBean csgServer){
return "https://" + csgServer.getHost() + ":" + csgServer.getPort() + "/services/";
}
private String getQpidJNDIString(Properties prop, String userName, String passWord){
StringBuffer jndiBuf = new StringBuffer();
Iterator itr = prop.keySet().iterator();
while (itr.hasNext()){
String key = (String) itr.next();
if(key.equals("connectionfactory.QueueConnectionFactory")){
continue;
}
jndiBuf.append(key).append("=").append((String)prop.get(key));
if(itr.hasNext()){
jndiBuf.append("&");
}
}
jndiBuf.append("&").append(CSGConstant.CSG_AMQP_USER_NAME).append("=").append(userName).
append("&").append(CSGConstant.CSG_AMQP_PASSWORD).append("=").append(passWord);
return jndiBuf.toString();
}
private String getRegistryJNDIString(String jndiString, String userName, String passWord) {
String jndiBuf = "";
String[] tokens = jndiString.split("\n");
for (String token : tokens) {
if (token.contains("connectionfactory.QueueConnectionFactory")) {
continue;
}
if (jndiBuf.equals("")) {
jndiBuf = jndiBuf + token;
} else {
jndiBuf = jndiBuf + "&" + token;
}
}
return jndiBuf + "&" + "userName=" + userName + "&passWord=" + passWord;
}
private CSGServerBean getCSGServerBean(String csgServerName) throws CSGException {
CSGServerBean bean = null;
try {
String resourceName = CSGConstant.REGISTRY_SERVER_RESOURCE_PATH + "/" + csgServerName;
if (registry.resourceExists(resourceName)) {
Resource resource = registry.get(resourceName);
try {
bean = new CSGServerBean();
bean.setHost(resource.getProperty(CSGConstant.HOST));
bean.setName(resource.getProperty(CSGConstant.NAME));
bean.setUserName(resource.getProperty(CSGConstant.USER_NAME));
bean.setPort(resource.getProperty(CSGConstant.PORT));
bean.setDomainName(resource.getProperty(CSGConstant.DOMAIN_NAME));
CryptoUtil cryptoUtil = CryptoUtil.getDefaultCryptoUtil();
bean.setPassWord(new String(cryptoUtil.base64DecodeAndDecrypt(
resource.getProperty("password"))));
} catch (CryptoException e) {
handleException("Cloud not convert into an AXIOM element");
}
} else {
throw new CSGException("Resource :" + resourceName + " does not exist");
}
} catch (RegistryException e) {
handleException("Cloud not retrieve the server information for server: " +
csgServerName, e);
}
return bean;
}
private void flagServicePublishing(String serviceName,
String serverName,
boolean isPublish,
boolean isJMSAlreadyEnabled)
throws CSGException {
try {
if (!registry.resourceExists(CSGConstant.REGISTRY_CSG_RESOURCE_PATH)) {
Collection collection = registry.newCollection();
registry.put(CSGConstant.REGISTRY_CSG_RESOURCE_PATH, collection);
}
Resource resource = registry.newResource();
Resource serverResource = registry.newResource();
String serverResourcePath = CSGConstant.REGISTRY_FLAG_RESOURCE_PATH + "/" +
serviceName + ".server";
if (isPublish) {
resource.setContent(CSGConstant.CSG_PUBLISHED_FLAG);
// FIXME: we need to think how we are going to support the case of publishing a
// service to more than one server
serverResource.setContent(serverName);
if (isJMSAlreadyEnabled) {
resource.addProperty(CSGConstant.CSG_IS_JMS_ENABLED, "true");
} else {
resource.addProperty(CSGConstant.CSG_IS_JMS_ENABLED, "false");
}
} else {
resource.setContent(CSGConstant.CSG_UN_PUBLISHED_FLAG);
// remove the published server from the list
if (registry.resourceExists(serverResourcePath)) {
registry.delete(serverResourcePath);
}
}
registry.put(CSGConstant.REGISTRY_FLAG_RESOURCE_PATH + "/" + serviceName + ".flag",
resource);
registry.put(CSGConstant.REGISTRY_FLAG_RESOURCE_PATH + "/" + serviceName + ".server",
serverResource);
} catch (RegistryException e) {
handleException("Cloud not flag the service '" + serviceName + "'", e);
}
}
private boolean isExposedOnJMS(String serviceName){
boolean isJMSEnabled = false;
try {
TransportSummary[] summary = transportAdmin.listExposedTransports(serviceName);
if(summary != null){
for(TransportSummary exposedTrp:summary){
if(CSGConstant.JMS_PREFIX.equals(exposedTrp.getProtocol())){
isJMSEnabled = true;
}
}
}
} catch (Exception e) {
isJMSEnabled = false;
}
return isJMSEnabled;
}
public static String getTenantId(AxisConfiguration axisConfig) {
SuperTenantCarbonContext carbonContext =
SuperTenantCarbonContext.getCurrentContext(axisConfig);
return String.valueOf(carbonContext.getTenantId());
}
private boolean hasInOutOperations(AxisService service) {
for (Iterator<AxisOperation> axisOpItr = service.getOperations(); axisOpItr.hasNext();) {
AxisOperation axisOp = axisOpItr.next();
if (axisOp.getAxisSpecificMEPConstant() == WSDLConstants.MEP_CONSTANT_IN_OUT) {
return true;
}
}
return false;
}
private void handleException(String msg, Throwable t) throws CSGException {
log.error(msg, t);
throw new CSGException(msg, t);
}
private void handleException(String msg) throws CSGException {
log.error(msg);
throw new CSGException(msg);
}
}