/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and others contributors as indicated
* by the @authors tag. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* (C) 2005-2010
*/
package org.jboss.soa.esb.listeners.gateway.camel;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import javax.jms.ConnectionFactory;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.sql.DataSource;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.NoTypeConversionAvailableException;
import org.apache.camel.RuntimeCamelException;
import org.apache.camel.TypeConverter;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.impl.DefaultPackageScanClassResolver;
import org.apache.camel.impl.JndiRegistry;
import org.apache.camel.model.Constants;
import org.apache.camel.model.RoutesDefinition;
import org.apache.camel.spi.PackageScanClassResolver;
import org.apache.camel.spi.TypeConverterRegistry;
import org.apache.log4j.Logger;
import org.dom4j.DocumentHelper;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.jboss.mx.util.MBeanServerLocator;
import org.jboss.soa.esb.ConfigurationException;
import org.jboss.soa.esb.addressing.eprs.JMSEpr;
import org.jboss.soa.esb.common.Configuration;
import org.jboss.soa.esb.helpers.ConfigTree;
import org.jboss.soa.esb.helpers.NamingContextException;
import org.jboss.soa.esb.helpers.NamingContextPool;
import org.jboss.soa.esb.listeners.lifecycle.AbstractManagedLifecycle;
import org.jboss.soa.esb.listeners.lifecycle.ManagedLifecycleException;
import org.jboss.soa.esb.util.JndiUtil;
/**
* <p>The CamelGateway leverages Apache Camel's input capabilities, translates the Camel Message to an ESB Message,
* and invokes the associated ESB Service.</p>
*
* <p><em>Configuration example:</em></p>
*
* <p><code><jbossesb ...><br/>
* <providers><br/>
* <camel-provider name="..."><br/>
* <camel-bus busid="..."><br/>
* <from uri="..."/><br/>
* ...<br/>
* </camel-bus><br/>
* </camel-provider><br/>
* </providers><br/>
* <services><br/>
* <listeners><br/>
* <camel-gateway name="..." busidref="..."/><br/>
* </listeners><br/>
* <actions><br/>
* <action .../><br/>
* </actions><br/>
* </services><br/>
* </jbossesb></code></p>
*
* <p>For more detailed information, please visit this wiki page:<br/>
* <a href="http://community.jboss.org/wiki/CamelGateway">http://community.jboss.org/wiki/CamelGateway</a><br/>
* or refer to the Programmer's Guide.</p>
*
* @author dward at jboss.org
*/
public class CamelGateway extends AbstractManagedLifecycle {
public static final String ROUTES = "routes";
private static final Logger logger = Logger.getLogger(CamelGateway.class);
private static final String AS6_CLASS_RESOLVER = "org.jboss.soa.esb.listeners.gateway.camel.as6.JBossPackageScanClassResolver";
private static final boolean isAS4;
private static final boolean isAS5;
private static final boolean isAS6;
static {
try {
MBeanServer mbeanServer = MBeanServerLocator.locateJBoss();
String versionNumber = (String)mbeanServer.getAttribute(new ObjectName("jboss.system:type=Server"), "VersionNumber");
isAS4 = (Integer.valueOf(versionNumber.substring(0, 1)).intValue() == 4);
isAS5 = (Integer.valueOf(versionNumber.substring(0, 1)).intValue() == 5);
isAS6 = (Integer.valueOf(versionNumber.substring(0, 1)).intValue() == 6);
} catch (Throwable t) {
throw new RuntimeException("problem detecting JBoss AS version", t);
}
}
private String routesXML = null;
private Properties jndiEnvironment = null;
private Context jndiContext = null;
private CamelContext camelContext = null;
public CamelGateway(ConfigTree config) throws ConfigurationException {
super(config);
for (ConfigTree property: config.getChildren("property")) {
if (ROUTES.equals(property.getRequiredAttribute("name"))) {
routesXML = property.getRequiredAttribute("value").trim();
break;
}
}
if (routesXML == null || routesXML.length() == 0) {
throw new ConfigurationException("property [" + ROUTES + "] missing or empty");
} else {
routesXML = routesXML.replaceAll("&", "&");
}
jndiEnvironment = JndiUtil.parseEnvironmentProperties(config);
jndiEnvironment.setProperty(Context.PROVIDER_URL, config.getAttribute(JMSEpr.JNDI_URL_TAG, Configuration.getJndiServerURL()));
jndiEnvironment.setProperty(Context.INITIAL_CONTEXT_FACTORY, config.getAttribute(JMSEpr.JNDI_CONTEXT_FACTORY_TAG, Configuration.getJndiServerContextFactory()));
jndiEnvironment.setProperty(Context.URL_PKG_PREFIXES, config.getAttribute(JMSEpr.JNDI_PKG_PREFIX_TAG, Configuration.getJndiServerPkgPrefix()));
}
@Override
protected void doInitialise() throws ManagedLifecycleException {
try {
jndiContext = NamingContextPool.getNamingContext(jndiEnvironment);
} catch (NamingContextException nce) {
throw new ManagedLifecycleException(nce);
}
// this allows us to use Camel's property replacement via ref:{{jndiName}} - actually (ref:%7B%7BjndiName%7D%7D)
camelContext = new DefaultCamelContext(new JndiRegistry(jndiContext));
// we need to disable JMX so we don't depend on Spring classes
camelContext.disableJMX();
// configure correct classloading
DefaultPackageScanClassResolver packageScanClassResolver = null;
if (isAS4) {
packageScanClassResolver = (DefaultPackageScanClassResolver) camelContext.getPackageScanClassResolver();
if (packageScanClassResolver == null) {
packageScanClassResolver = new DefaultPackageScanClassResolver();
camelContext.setPackageScanClassResolver(packageScanClassResolver);
}
} else if (isAS5) {
// JBossESB on AS5+ needs this to handle VFS classloading URIs.
// JBossESB on AS4 will not work if we use this.
packageScanClassResolver = new JBossPackageScanClassResolver();
camelContext.setPackageScanClassResolver(packageScanClassResolver);
} else if (isAS6) {
try {
Class c = Class.forName(AS6_CLASS_RESOLVER);
Constructor con = c.getConstructor();
packageScanClassResolver= (DefaultPackageScanClassResolver) con.newInstance();
camelContext.setPackageScanClassResolver(packageScanClassResolver);
} catch (InstantiationException e) {
throw new ManagedLifecycleException(e);
} catch (ClassNotFoundException e) {
throw new ManagedLifecycleException(e);
} catch (SecurityException e) {
throw new ManagedLifecycleException(e);
} catch (NoSuchMethodException e) {
throw new ManagedLifecycleException(e);
} catch (IllegalArgumentException e) {
throw new ManagedLifecycleException(e);
} catch (IllegalAccessException e) {
throw new ManagedLifecycleException(e);
} catch (InvocationTargetException e) {
throw new ManagedLifecycleException(e);
}
camelContext.setPackageScanClassResolver(packageScanClassResolver);
}
Set<ClassLoader> classLoaders = new HashSet<ClassLoader>();
classLoaders.add(Thread.currentThread().getContextClassLoader());
packageScanClassResolver.setClassLoaders(classLoaders);
// register JndiTypeConverters
Set<Class<?>> jndiTypes = new HashSet<Class<?>>();
jndiTypes.add(ConnectionFactory.class);
jndiTypes.add(DataSource.class);
// TODO: add classes from comma-separated config property for more jndiTypes that get looked up?
TypeConverterRegistry typeConverterRegistry = camelContext.getTypeConverterRegistry();
TypeConverter jndiTypeConverter = new JndiTypeConverter(jndiContext);
for (Class<?> jndiType : jndiTypes) {
typeConverterRegistry.addTypeConverter(jndiType, String.class, jndiTypeConverter);
}
camelContext.addComponent(JBossESBComponent.JBOSSESB, new JBossESBComponent(getConfig()));
if (logger.isDebugEnabled()) {
try {
StringWriter sw = new StringWriter();
OutputFormat of = OutputFormat.createPrettyPrint();
of.setSuppressDeclaration(true);
new XMLWriter(sw, of).write(DocumentHelper.parseText(routesXML));
logger.debug("adding routes [" + sw.toString().replaceAll("&", "&") + "]");
} catch (Exception e) {
logger.warn("problem pretty-printing routes: " + e.getMessage());
logger.debug("adding routes [" + routesXML.replaceAll("&", "&") + "]");
}
}
try {
JAXBContext jaxbContext = JAXBContext.newInstance(Constants.JAXB_CONTEXT_PACKAGES);
Unmarshaller um = jaxbContext.createUnmarshaller();
RoutesDefinition rd = (RoutesDefinition)um.unmarshal(new StringReader(routesXML));
camelContext.addRouteDefinitions(rd.getRoutes());
} catch (Exception e) {
throw new ManagedLifecycleException("problem adding routes", e);
}
}
@Override
protected void doStart() throws ManagedLifecycleException {
try {
camelContext.start();
} catch (Exception e) {
throw new ManagedLifecycleException("problem starting CamelContext", e);
}
}
@Override
protected void doStop() throws ManagedLifecycleException {
try {
camelContext.stop();
} catch (Exception e) {
throw new ManagedLifecycleException("problem stopping CamelContext", e);
}
}
@Override
protected void doDestroy() throws ManagedLifecycleException {
camelContext = null;
if (jndiContext != null) {
try {
NamingContextPool.releaseNamingContext(jndiContext);
} catch (NamingContextException nce) {
throw new ManagedLifecycleException(nce);
} finally {
jndiContext = null;
}
}
}
private static class JndiTypeConverter implements TypeConverter {
private Context jndiContext;
private JndiTypeConverter(Context jndiContext) {
this.jndiContext = jndiContext;
}
public <T> T convertTo(Class<T> type, Object value) {
if (value instanceof String) {
String jndiName = (String)value;
Object jndiObject;
try {
jndiObject = jndiContext.lookup(jndiName);
} catch (NamingException ne) {
throw new RuntimeCamelException(ne);
}
return type.cast(jndiObject);
}
return null;
}
public <T> T convertTo(Class<T> type, Exchange exchange, Object value) {
return convertTo(type, value);
}
public <T> T mandatoryConvertTo(Class<T> type, Object value) throws NoTypeConversionAvailableException {
return convertTo(type, value);
}
public <T> T mandatoryConvertTo(Class<T> type, Exchange exchange, Object value) throws NoTypeConversionAvailableException {
return convertTo(type, value);
}
}
}