PDA

View Full Version : Catalina, Spring DM, Equinox, and JNDI


jmcginn
May 27th, 2008, 10:56 AM
Hi all,

I am trying to do the following:

1. Launch Equinox via the Eclipse Servlet Bridge within Tomcat.
2. Develop a set of bundles for a cumstomizable notification service including a whole set of services.
3. Access various resources (message queues, data sources) defined in tomcat's server.xml/web.xml from my bundles
4. Use spring DM and spring JNDI support to wire up these resources and inject them into my bundle services.

I have succeeded in steps 1 & 2 and have the servlet bridge up and running in Tomcat and it is launching Equinox. I have installed and launched a set of bundles including my own bundles and a bunch of dependencies.

I am failing however with steps 3 & 4. Every time I try to get a JNDI resource I get the dreaded NameNotFound exception. I have tried writing my own JNDI lookups and debugged the code and my InitialContext appears to be empty.

I have so far tried the following:
#1. Adding resources to the main Tomcat server.xml
#2. Defining a separate server.xml (with the resources defined in there) as a fragment and attaching it to the catalina start bundle

I am trying initially with a plain string that looks like this in my server.xml
<Environment name="myTestValue" value="Hello OSGi" type="java.lang.String"/>

Can anyone give me any direction on where to look next on how to access to these resources?

Or is there a better way then JNDI to define environment specific resources. Note: I have to have the following:
1. An environment agnostic WAR that can be deployed in different environments with no changes to the WAR (dev. test. production)
2. An OSGi environment that is launched from Tomcat :(

Thanks

John

Costin Leau
May 27th, 2008, 01:35 PM
Hi John,

I'm glad to hear you have thing working. Your problem is not an easy one since the servlet bridge, as far as I know, doesn't handle the JNDI entries. Which means whatever Tomcat configuration you have in there, it's not going to be available inside OSGi.
You can probably use some sort of remoting so the Tomcat JNDI can be seen inside OSGi but this can be difficult since different class loaders are used which means the items in these two worlds are really incompatible unless you make sure that OSGi delegates everything to its boot classloader so you don't run into class cast exception problems.
Have you tried using Spring-DM web support? it integrates natively with Tomcat and Jetty meaning it doesn't hide the fact container flavour from you. Moreover, the servers are installed as OSGi services so there is no nothing of outside-OSGi.

jmcginn
May 27th, 2008, 02:02 PM
Hi Costin,

Yeah that's kind of what I figured with Tomcat and the servlet bridge. I am using the Spring DM web support, but my equinox framework is still being launched by the servlet bridge still :(.

I tried using Spring DM Web along with the catalina start bundle and attached a fragment with a server.xml that defined my resource, but I still can't seem to get a hold of any resources from another bundle.

For instance I have the following:
79 ACTIVE org.springframework.osgi.catalina.start.osgi_1.0.0 .SNAPSHOT
Fragments=97
97 RESOLVED ServerConf_1.0.0
Master=79

In my ServerConf bundle I have conf/server.xml with the following:

<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector port="8084" />
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" unpackWARs="false" autoDeploy="false"
liveDeploy="false" deployOnStartup="false"
xmlValidation="false" xmlNamespaceAware="false">\
<DefaultContext useNaming="true" reloadable="true">
<Environment name="myTestValue" value="Hello OSGi" type="java.lang.String"/>
</DefaultContext>
</Host>
</Engine>
</Service>
</Server>


The server configuration seems to be working as I can actually hit the 8084 port, but I still can't get a hold of the JNDI resource.

*****

I am not tied to using JNDI to configure resources, and could use another possibly Spring based option. As long as it fulfills my requirements above where I have a completely environment agnostic WAR and all of my environment based resources live externally to it. My goals are:
1. Deploy the WAR in different environs (test, dev, prod)
2. Simply application configuration as much as possbile for my sys admins.

Thanks,

John

Costin Leau
May 28th, 2008, 04:01 AM
JNDI sounds like a great candidate for your case since it's available in Tomcat, Jetty and all application servers. You could potentially define your settings as beans or properties which are injected into your application so instead of relying on JNDI to have this values set, you would simply use Spring to get a hold of your configuration (this implies that you will have to deploy the configuration managed by Spring as well, along or before your WAR).
You could use something like SingletonLocatorBeanFactory to have only one instance of your configuration per class loader.

jmcginn
May 28th, 2008, 01:39 PM
Hi Costin,

Good news I have made quite a bit of progress.

I managed to use the eclipse servlet bridge to launch equinox and actually deploy a WAR bundle and actually get to its html pages and servlets :D

I was also able to lookup a JNDI resource that I defined in the web.xml with the env-entry element.

Now the big next issue and hopefully my final one to get up and running is to move these delcarations out of my WAR file and into a more centralized piece (e.g. server.xml or more appropriate a context xml file under conf).

I have attached a server.xml bundle fragment successfully, but this is still not the ideal situation. I would prefer my sys admins manage files on the file system not OSGi bundles which they probably are not familiar with. Is there a way to start up the Tomcat osgi start service so that it launches tomcat with a specific home so that it will find normal server.xml/context.xml files sitting there?

Thanks,

John

Costin Leau
May 29th, 2008, 02:50 AM
For your case, I think it would be easier to actually create a special service inside JNDI which can search for your configuration on the file system and make that available inside JNDI. Since with Tomcat, the JNDI is read-only, you could change that by using a different provider and instruct that to read the configuration from the file or populate it with your configuration - populator.
Since Tomcat configuration is read from the bundle space and not the filesystem, there isn't much you can do in this regard expect override the Tomcat activator to plug in your own config.

jmcginn
Jun 3rd, 2008, 03:06 PM
Hey Costin, thanks for the help, I think I have everything working now except for one issue that I think is a timing issue. I had to write my own Tomcat deployer and launcher to get to work, but finally got it all there.

My issue has to do with the spring web extender loading a WAR bundle and spring config loading JNDI resources bound in that WAR's context.

I have a WAR bundle that deploys to a fixed catalina base and points to a fixed context xml file where I can define some resources. This loads fine and loads the resources and makes them available in the context. However it appears that the spring application context is loaded by the osgi extender before the WAR bundle is deployed and started so my JNDI resources are not available yet :(

Hold on I just figured it out in mid-thought >:D, I won't use the OSGi extender to load my WAR's spring context, I'll use the normal servlet context loader and all should be good :D

Costin Leau
Jun 4th, 2008, 04:07 AM
For war deployment, using files under META-INF/spring is not needed since you end up, as you already discovered, with two different lifecycles. Just let the servlet container start your files are you should be done.
I'm curios though to know why did you have to create your own Tomcat deployer and launcher

jmcginn
Jun 4th, 2008, 08:29 AM
Hey Costin,

I had to do this for several reasons related to how our tech services guys lock down our deployment environment and how we host a whole bunch of applications on a single cluster. All of our configuration is controlled by them. I also had to work around the fact that I was launching this from the Equniox servlet bridge.

So in short my Tomcat Launcher allows a system property (osgi.catalina.base) to pass in to specify a different catalina.base that the launched catalina bundle will use. This is so it can find the server.xml, etc. sitting on the file system somewhere.

Second the Deployer extended your WAR deployer and overrode the createDeployment method so that it would use the osgi.catalina.base folder as the doc base and not do the temporary folder but instead use a permanent folder under the normal webapps folder. This allowed me to setup a normal context xml file under the normal {osgi.catalina.base}/conf/{engine name}/{host name}/{context name}.xml

Next at some point I will implement the getHost method to be a little more robust so it doesn't just default to the first host. Trying to think about how to handle that one still.

I also implemented our own context path strategy that honored the Webapp-Context MF header.

FYI: Here is the main code in the Tomcat Launcher

private StandardService createCatalinaServer(Bundle bundle) throws Exception {

OsgiCatalina catalina = new OsgiCatalina();
catalina.setAwait(false);
catalina.setUseShutdownHook(false);
catalina.setName("Catalina");
catalina.setParentClassLoader(Thread.currentThread ().getContextClassLoader());

String osgiBase = System.getProperty("osgi.catalina.base");
File base = new File(osgiBase);
File confFile = null;

if(base.exists() && base.isDirectory() && base.canRead()) {
File confBase = new File(base, "conf");
if(confBase.exists() && confBase.isDirectory() && confBase.canRead()) {
confFile = new File(confBase, "server.xml");
} else {
throw new IOException(String.format("The defined osgi.catalina.base %s/conf does not exist or Tomcat does not have read priviledges", osgiBase));
}
} else {
throw new IOException(String.format("The defined osgi.catalina.base %s does not exist or Tomcat does not have read priviledges", osgiBase));
}

if(confFile.exists() && confFile.isFile() && confFile.canRead()) {
catalina.setCatalinaBase(osgiBase);
catalina.setConfigFile(confFile.getAbsolutePath()) ;
catalina.load();

} else {
throw new IOException(String.format("The defined osgi.catalina.base %s/conf/server.xml does not exist or Tomcat does not have read priviledges", osgiBase));
}

Server server = catalina.getServer();

return (StandardService) server.findServices()[0];
}


The main thing I changed in the WAR deployer was how docBase was determined:

private File getDocBase(String contextPath) {
File docBase = null;

if((docBase = getFileFromPropKey(OSGI_CATALINA_BASE_PROP_KEY, contextPath)) == null) {
if((docBase = getFileFromPropKey(CATALINA_BASE_PROP_KEY, contextPath)) == null) {
if((docBase = getFileFromPropKey(CATALINA_HOME_PROP_KEY, contextPath)) == null) {
System.out.println(String.format("No valid docBase folder was found in the system properties: %s, %s, or %s, " +
"using the user's home directory", OSGI_CATALINA_BASE_PROP_KEY, CATALINA_BASE_PROP_KEY, CATALINA_HOME_PROP_KEY));
if((docBase = getFileFromPropKey(USER_HOME_PROP_KEY, contextPath)) == null) {
System.out.println("No valid docBase was found!! WAR cannot be deployed");
}
}
}
}

return docBase;
}


I think I will remove the cascading at some point as we will always deploy to the value in osgi.catalina.base.


Hope that helps clarify.

jmcginn
Jun 4th, 2008, 08:47 AM
Costin,

I have a couple of more ?'s and I think I will be there!

Can I use the osgi namespace in my config file that I'm firing up in in my web bundle? I tried to do it but I get the "Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/osgi]" exception. (This is if I try to load up the context in code by creating a ApplicationContext)

If I try and setup a context loader listener I get this exception: "java.lang.IllegalStateException: Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!"
I want to correct this, after debugging thru spring's ContextLoader I see that I am actually getting the same exception as above WRT to the osgi namespace.

----
Lastly how do I setup logging? I have a log4j.properties file sitting in my bundle under WEB-INF/classes but the log config seems to not still be working. Does it need to be in the root of the bundle maybe?


Thanks

John

Costin Leau
Jun 5th, 2008, 01:32 AM
Thanks for sharing - interesting approach. By the way,in TRUNK the Webapp-Context header has been supported for quite some time in the default strategy. As for the Tomcat WAR deployer, if you'd like to see some methods overriden for easier extension just raise some issues on JIRA and we'll sort them out.

Costin Leau
Jun 5th, 2008, 01:36 AM
The namespace are supported as long as:
- the spring-dm extender is running
- the bundles declaring the namespace are installed in the platform

Turn on logging and see search for the appropriate Spring-DM messages.
As for logging, it's the bundle classpath that counts - normally the root of the bundle is part of the classpath so placing a log4j.properties file in there should work (this is what we use internally in Spring-DM).

jmcginn
Jun 5th, 2008, 11:53 AM
Costin,

I have a log4j.properties file sitting the root of my bundle and I am still not seeing any logging? Does it matter what bundle I put it in? Does it need to be a fragment attached to some other bundle? I am having all kinds of classpath related issues and cannot get any logging to work :(

Thanks

John

Costin Leau
Jun 5th, 2008, 01:37 PM
The log4j.properties should be picked up by any bundle that imports the logging package. In Spring-DM the classes use commons logging API which uses slf4j as an implementation which is statically wired to log4j.
Placing log4j.properties in such a bundle triggers logging since the commons classes get resolved to log4j which reads the classpath and picks up log4j.properties.
There is nothing special that we're doing...