/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.apache.cxf.jca.cxf;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.logging.Logger;
import javax.ejb.EJBHome;
import javax.ejb.EJBObject;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.resource.ResourceException;
import javax.rmi.PortableRemoteObject;
import javax.xml.bind.JAXBException;
import javax.xml.namespace.QName;
import org.apache.cxf.Bus;
import org.apache.cxf.BusException;
import org.apache.cxf.BusFactory;
import org.apache.cxf.binding.BindingFactoryManager;
import org.apache.cxf.binding.soap.SoapBindingFactory;
import org.apache.cxf.binding.soap.SoapTransportFactory;
import org.apache.cxf.common.util.PackageUtils;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.frontend.ServerFactoryBean;
import org.apache.cxf.jaxb.JAXBDataBinding;
import org.apache.cxf.jaxws.JAXWSMethodInvoker;
import org.apache.cxf.jaxws.support.JaxWsServiceFactoryBean;
import org.apache.cxf.jca.core.resourceadapter.ResourceAdapterInternalException;
import org.apache.cxf.jca.core.resourceadapter.UriHandlerInit;
import org.apache.cxf.service.Service;
import org.apache.cxf.service.factory.ReflectionServiceFactoryBean;
import org.apache.cxf.transport.ConduitInitiatorManager;
import org.apache.cxf.transport.DestinationFactoryManager;
import org.apache.cxf.transport.http.AbstractHTTPTransportFactory;
import org.apache.cxf.transport.http_jetty.JettyHTTPTransportFactory;
import org.apache.cxf.wsdl.WSDLManager;
import org.apache.cxf.wsdl11.WSDLManagerImpl;
public class JCABusFactory {
private static final Logger LOG = Logger.getLogger(JCABusFactory.class.getName());
private Bus bus;
private BusFactory bf;
private List<Server> servantsCache = new ArrayList<Server>();
private InitialContext jndiContext;
private ClassLoader appserverClassLoader;
private ManagedConnectionFactoryImpl mcf;
private Object raBootstrapContext;
public JCABusFactory(ManagedConnectionFactoryImpl aMcf) {
this.mcf = aMcf;
}
protected String[] getBusArgs() throws ResourceException {
//There is only setting up the BUSID
String busId = mcf.getConfigurationScope();
LOG.config("BUSid=" + busId);
String busArgs[] = new String[2];
busArgs[0] = "-BUSid";
busArgs[1] = busId;
return busArgs;
}
protected Bus createBus(ClassLoader loader) throws ResourceException {
try {
//REVISIT we need to use the CXF defualt BusFactory
bf = org.apache.cxf.BusFactory.newInstance(getBusClassName());
bus = bf.createBus();
} catch (Exception ex) {
throw new ResourceAdapterInternalException("Failed to initialize cxf runtime", ex);
}
return bus;
}
protected void initBus() throws ResourceException {
//REVISIT The Bus need to be init with context for better configuration
try {
SoapBindingFactory bindingFactory = new SoapBindingFactory();
bus.getExtension(BindingFactoryManager.class)
.registerBindingFactory("http://schemas.xmlsoap.org/wsdl/soap/", bindingFactory);
DestinationFactoryManager dfm = bus.getExtension(DestinationFactoryManager.class);
SoapTransportFactory soapDF = new SoapTransportFactory();
soapDF.setBus(bus);
dfm.registerDestinationFactory("http://schemas.xmlsoap.org/wsdl/soap/", soapDF);
dfm.registerDestinationFactory("http://schemas.xmlsoap.org/soap/", soapDF);
AbstractHTTPTransportFactory httpTransport = new JettyHTTPTransportFactory();
dfm.registerDestinationFactory("http://schemas.xmlsoap.org/wsdl/http", httpTransport);
//dfm.registerDestinationFactory("http://schemas.xmlsoap.org/wsdl/soap/http", httpTransport);
//dfm.registerDestinationFactory("http://cxf.apache.org/bindings/xformat", httpTransport);
ConduitInitiatorManager extension = bus.getExtension(ConduitInitiatorManager.class);
//extension.registerConduitInitiator(LocalTransportFactory.TRANSPORT_ID, httpTransport);
extension.registerConduitInitiator("http://schemas.xmlsoap.org/wsdl/soap/http", httpTransport);
extension.registerConduitInitiator("http://schemas.xmlsoap.org/http/http", httpTransport);
//extension.registerConduitInitiator("http://schemas.xmlsoap.org/soap/", httpTransport);
bus.setExtension(new WSDLManagerImpl(), WSDLManager.class);
} catch (Exception ex) {
throw new ResourceAdapterInternalException("Failed to initialize cxf runtime", ex);
}
}
protected synchronized void init() throws ResourceException {
LOG.config("initialising... the bus");
new UriHandlerInit();
ClassLoader original = Thread.currentThread().getContextClassLoader();
try {
ClassLoader cl = this.getClass().getClassLoader();
// ensure resourceadapter: url handler can be found by URLFactory
Thread.currentThread().setContextClassLoader(cl);
//TODO Check for the managed connection factory properties
//TODO We may need get the configuration file from properties
//mcf.validateProperties();
bus = createBus(cl);
initBus();
initialiseServants();
} catch (Exception ex) {
if (ex instanceof ResourceAdapterInternalException) {
throw (ResourceException)ex;
} else {
ex.printStackTrace();
throw new ResourceAdapterInternalException("Failed to initialize connector runtime", ex);
}
} finally {
Thread.currentThread().setContextClassLoader(original);
}
}
void initialiseServants() throws ResourceException {
if (isMonitorEJBServicePropertiesEnabled()) {
LOG.info("ejb service properties update enabled. ");
startPropertiesMonitorThread();
} else {
URL propsUrl = mcf.getEJBServicePropertiesURLInstance();
if (propsUrl != null) {
initialiseServantsFromProperties(loadProperties(propsUrl), false);
}
}
}
void initialiseServantsFromProperties(Properties ejbServants, boolean abortOnFailure)
throws ResourceException {
// Props format: jndi name = service string
//
// java:/ejbs/A={http:/a/b/b}SoapService@http://localost:wsdls/a.wsdl
try {
jndiContext = new InitialContext();
} catch (NamingException ne) {
throw new ResourceAdapterInternalException(
"Failed to construct InitialContext for EJBServant(s) jndi lookup, reason: "
+ ne, ne);
}
deregisterServants(bus);
LOG.config("Initialising EJB endpoints...");
Enumeration keys = ejbServants.keys();
while (keys.hasMoreElements()) {
String jndiName = (String)keys.nextElement();
String serviceName = (String)ejbServants.getProperty(jndiName);
LOG.config("Found ejb endpoint: jndi name=" + jndiName + ", wsdl service=" + serviceName);
try {
initialiseServant(jndiName, serviceName);
} catch (ResourceException re) {
LOG.warning("Error initialising servant with jndi name "
+ jndiName + " and service name "
+ serviceName + " Exception:"
+ re.getMessage());
if (abortOnFailure) {
throw re;
}
}
}
}
void initialiseServant(String jndiName, String serviceName) throws ResourceException {
Server servant = null;
EJBObject ejb = null;
QName serviceQName = null;
String nameSpace = "";
String interfaceName = "";
String packageName = "";
Class interfaceClass = null;
ClassLoader ejbClassLoader = null;
ClassLoader currentThreadContextClassLoader = null;
try {
if ("".equals(serviceName)) {
throw new ResourceAdapterInternalException(
"A WSDL service QName must be specified as the value of the EJB JNDI name key: "
+ jndiName);
} else {
serviceQName = serviceQNameFromString(serviceName);
serviceQNameFromString(serviceName);
// Get ejbObject
ejb = getEJBObject(jndiName);
ejbClassLoader = ejb.getClass().getClassLoader();
//NOTE we can use the ejbClassLoader to load WSDL
currentThreadContextClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(ejbClassLoader);
Thread.currentThread().setContextClassLoader(currentThreadContextClassLoader);
nameSpace = serviceQName.getNamespaceURI();
packageName = PackageUtils.parsePackageName(nameSpace, null);
// Get interface of ejbObject
interfaceName = jndiName.substring(0, jndiName.length() - 4);
interfaceName = packageName + "." + interfaceName;
interfaceClass = Class.forName(interfaceName);
// NOTE We can check the annoation to descide which kind of frontend we will use
// Almostly we just need to use the jax-ws frontend
// If we have wsdl , then wsdl first,
// else code first
servant = publishServantWithoutWSDL(ejb, jndiName, nameSpace, interfaceClass);
}
} catch (Exception e) {
throw new ResourceAdapterInternalException(e.getMessage());
}
synchronized (servantsCache) {
if (servant != null) {
servantsCache.add(servant);
}
}
}
public Server publishServantWithoutWSDL(EJBObject ejb, String jndiName,
String nameSpace, Class interfaceClass)
throws Exception {
String hostName = "";
try {
InetAddress addr = InetAddress.getLocalHost();
//hostName = addr.getHostName();
hostName = addr.getCanonicalHostName();
} catch (UnknownHostException e) {
e.printStackTrace();
}
String address = "http://" + hostName + ":9999/" + jndiName;
ReflectionServiceFactoryBean bean = new JaxWsServiceFactoryBean();
Service service = createService(interfaceClass, bean);
// REVISIT this is easy to be replace by EJBMethodInvoker
service.setInvoker(new JAXWSMethodInvoker(ejb));
ServerFactoryBean svrFactory = new ServerFactoryBean();
return createServer(svrFactory, bean, address);
}
protected Service createService(Class interfaceClass, ReflectionServiceFactoryBean serviceFactory)
throws JAXBException {
serviceFactory.setDataBinding(new JAXBDataBinding());
serviceFactory.setBus(bus);
serviceFactory.setServiceClass(interfaceClass);
Map<String, Object> props = new HashMap<String, Object>();
props.put("test", "test");
serviceFactory.setProperties(props);
return serviceFactory.create();
}
protected Server createServer(ServerFactoryBean serverFactory,
ReflectionServiceFactoryBean serviceFactory,
String address) {
serverFactory.setAddress(address);
serverFactory.setTransportId("http://schemas.xmlsoap.org/soap/http");
serverFactory.setServiceFactory(serviceFactory);
serverFactory.setBus(bus);
return serverFactory.create();
}
private EJBObject getEJBObject(String jndi) throws BusException {
try {
EJBHome home = getEJBHome(jndiContext, jndi);
// ejbHomeClassLoader = home.getClass().getClassLoader();
Method createMethod = home.getClass().getMethod("create", new Class[0]);
return (EJBObject) createMethod.invoke(home, new Object[0]);
} catch (NamingException e) {
throw new BusException(e);
} catch (NoSuchMethodException e) {
throw new BusException(e);
} catch (IllegalAccessException e) {
throw new BusException(e);
} catch (InvocationTargetException itex) {
Throwable thrownException = itex.getTargetException();
throw new BusException(thrownException);
}
}
protected EJBHome getEJBHome(Context ejbContext, String jndiName) throws NamingException {
Object obj = ejbContext.lookup(jndiName);
return (EJBHome) PortableRemoteObject.narrow(obj, EJBHome.class);
}
void startPropertiesMonitorThread() throws ResourceException {
Integer pollIntervalInteger = mcf.getEJBServicePropertiesPollInterval();
int pollInterval = pollIntervalInteger.intValue();
LOG.info("ejb service properties poll interval is : " + pollInterval + " seconds");
EJBServicePropertiesMonitorRunnable r = new EJBServicePropertiesMonitorRunnable(pollInterval);
Thread t = new Thread(r);
t.setDaemon(true);
t.start();
}
boolean isMonitorEJBServicePropertiesEnabled() throws ResourceException {
boolean retVal = false;
if (mcf.getMonitorEJBServiceProperties().booleanValue()) {
URL url = mcf.getEJBServicePropertiesURLInstance();
if (url == null) {
throw new ResourceAdapterInternalException(
"MonitorEJBServiceProperties property is set to true,"
+ " but EJBServicePropertiesURL is not set. "
+ "Both properties must be set to enable monitoring.");
}
retVal = isFileURL(url);
}
return retVal;
}
boolean isFileURL(URL url) {
return url != null && "file".equals(url.getProtocol());
}
protected void deregisterServants(Bus aBus) {
synchronized (servantsCache) {
if (!servantsCache.isEmpty()) {
Iterator<Server> servants = servantsCache.iterator();
while (servants.hasNext()) {
Server servant = servants.next();
servant.stop();
}
servantsCache.clear();
}
}
}
Properties loadProperties(URL propsUrl) throws ResourceException {
Properties props = null;
InputStream istream = null;
LOG.info("loadProperties, url=" + propsUrl);
try {
istream = propsUrl.openStream();
} catch (IOException ioe) {
throw new ResourceAdapterInternalException("Failed to openStream to URL, value=" + propsUrl
+ ", reason:" + ioe, ioe);
}
try {
props = new Properties();
props.load(istream);
} catch (IOException ioe) {
props = null;
throw new ResourceAdapterInternalException("Failed to load properties from " + propsUrl, ioe);
} finally {
try {
istream.close();
} catch (IOException ignored) {
//do nothing here
}
}
return props;
}
QName serviceQNameFromString(String qns) throws ResourceAdapterInternalException {
String lp = null;
String nameSpace = null;
// String re = "(\[(.*)\])?([^\@]+)(@?(.*))??";
// String[] qna = qns.split("(\[?+[^\]]*\])([^@])@?+(.*)");
try {
StringTokenizer st = new StringTokenizer(qns, "{},@", true);
while (st.hasMoreTokens()) {
String t = st.nextToken();
if ("{".equals(t)) {
nameSpace = st.nextToken();
st.nextToken();
// consume '}'
} else if (",".equals(t)) {
st.nextToken();
// consume 'portName'
} else if ("@".equals(t)) {
st.nextToken();
// consume 'wsdlLoc'
} else {
lp = t;
}
}
} catch (java.util.NoSuchElementException nsee) {
throw new ResourceAdapterInternalException(
"Incomplete QName, string is not in expected format: "
+ "[{namespace}]local part[@ wsdl location url]. value:"
+ qns, nsee);
}
LOG.fine("QN=" + qns + ", ns=" + nameSpace + ", lp=" + lp);
return new QName(nameSpace, lp);
}
String portNameFromString(String qns) throws ResourceAdapterInternalException {
String portName = null;
try {
StringTokenizer st = new StringTokenizer(qns, ",@", true);
while (st.hasMoreTokens()) {
String t = st.nextToken();
if (",".equals(t)) {
if (portName != null) {
throw new ResourceAdapterInternalException(
"portName already set, string is not in expected format:"
+ " [{namespace}]serviceName[,portName][@ wsdl location url]. value:"
+ qns);
}
portName = st.nextToken();
if ("@".equals(portName)) {
throw new ResourceAdapterInternalException(
"Empty portName, string is not in expected format: "
+ "[{namespace}]serviceName[,portName][@ wsdl location url]. value:"
+ qns);
}
}
}
} catch (java.util.NoSuchElementException nsee) {
throw new ResourceAdapterInternalException(
"Incomplete QName, string is not in expected format: "
+ "[{namespace}]serviceName[,portName][@ wsdl location url]. value:"
+ qns, nsee);
}
return portName;
}
String wsdlLocFromString(String qns) {
String wloc = null;
StringTokenizer st = new StringTokenizer(qns, "@", true);
while (st.hasMoreTokens()) {
String t = st.nextToken();
if ("@".equals(t)) {
wloc = st.nextToken();
}
}
return wloc;
}
private String getBusClassName() {
return System.getProperty("test.bus.class", "org.apache.cxf.bus.spring.SpringBusFactory");
}
protected List getRegisteredServants() {
return servantsCache;
}
public ClassLoader getAppserverClassLoader() {
return appserverClassLoader;
}
public void setAppserverClassLoader(ClassLoader classLoader) {
this.appserverClassLoader = classLoader;
}
public InitialContext getInitialContext() {
return jndiContext;
}
public Object getBootstrapContext() {
return raBootstrapContext;
}
public Bus getBus() {
return bus;
}
public void setBus(Bus b) {
bus = b;
}
public void create(ClassLoader classLoader, Object context) throws ResourceException {
this.appserverClassLoader = classLoader;
this.raBootstrapContext = context;
init();
}
class EJBServicePropertiesMonitorRunnable implements Runnable {
private long previousModificationTime;
private final int pollIntervalSeconds;
private final File propsFile;
private boolean continuing = true;
EJBServicePropertiesMonitorRunnable(int pollInterval) throws ResourceException {
pollIntervalSeconds = pollInterval;
propsFile = new File(mcf.getEJBServicePropertiesURLInstance().getPath());
}
public void setContinue(boolean c) {
this.continuing = c;
}
public void run() {
do {
try {
if (isPropertiesFileModified()) {
LOG.info("ejbServicePropertiesFile modified, initialising/updating servants");
initialiseServantsFromProperties(loadProperties(propsFile.toURI().toURL()), false);
}
Thread.sleep(pollIntervalSeconds * 1000);
} catch (Exception e) {
LOG.info("MonitorThread: failed to initialiseServantsFromProperties "
+ "with properties absolute path="
+ propsFile.getAbsolutePath() + ", reason: " + e.toString());
}
} while (continuing);
}
protected boolean isPropertiesFileModified() throws ResourceException {
boolean fileModified = false;
if (propsFile.exists()) {
long currentModificationTime = propsFile.lastModified();
if (currentModificationTime > previousModificationTime) {
previousModificationTime = currentModificationTime;
fileModified = true;
}
}
return fileModified;
}
}
// for unit test
protected void setBootstrapContext(Object ctx) {
raBootstrapContext = ctx;
}
}