Wednesday, October 13, 2010

Set optional jetty-env.xml for JNDI in maven jetty plugins

I talked about how to set up jndi with jetty in last post. But I still like to use "jetty:run" in the maven jetty plugin. It uses an embed server that hard to configure. Later I found a way to specify a non-conventional position for jetty-env.xml in maven-jetty-plugin. So I put jetty-env.xml in that position and specify it in pom.xml. Now I can still use jetty:run. But when it packaged as war and deployed in web container. It cannot run without setting JNDI in server.

Here is my setting:
1. Put jetty-env.xml in WEB-INF/local/jetty-env.xml;
2. Add following into pom.xml

<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<version>6.1.22</version>
<configuration>

<contextPath>/</contextPath>

<jettyEnvXml>src/main/webapp/WEB-INF/local/jetty-env.xml</jettyEnvXml>
.......
</configuration>
</plugin>


Well, if you really hate any settings in svn repository. You can put jetty-env.xml somewhere outside svn directory. Use

<jettyEnvXml>${jetty.setting}</jettyEnvXml>

Now when you start maven append the system property jetty.setting=directory.

mvn -Djetty.setting=**/jetty-env.xml jetty:run

Friday, October 8, 2010

Use JNDI with Spring/Jetty

We used to bury the settings (databases, remote calls urls, ...) in Maven's setting.xml. We keep several profiles for settings for different servers and use maven's profile selector to switch between them. This approach hide the sensitive information (username/password to databses...) in our local machines and switch between different profiles fairly effectively. However, it is painful to keep settings.xml sync between us, especially when we changes setting in server pretty frequently. So we decide to move to JNDI, so the setting goes with the servlet server (Jetty now, maybe some more powerful stuff later) and no more sync problem.

It took me three full days! Unfortunately, it becomes another typical experience with the Configuration Nightmare. One spends hours after hours read stuff all over internet and nothing makes sense, until finally, it makes sense and actually is QUITE SIMPLE!

Two pages are mostly useful. Jetty's JNDI page and this one (a little out-of-date and a few flaw).

So set up the resources in JNDI first. We need two types: DataSource for databases and normal Strings for remote call urls.

1. First of all, jetty by default does not support jndi. So we need to tell jetty to turn it on. (Modify your etc/jetty.xml file.)
<Array id="plusConfig" type="java.lang.String"> -->
   <Item>org.mortbay.jetty.webapp.WebInfConfiguration</Item>
   <Item>org.mortbay.jetty.plus.webapp.EnvConfiguration</Item>
   <Item>org.mortbay.jetty.plus.webapp.Configuration</Item>
   <Item>org.mortbay.jetty.webapp.JettyWebXmlConfiguration</Item>
   <Item>org.mortbay.jetty.webapp.TagLibConfiguration</Item>
 </Array>

<Call name="addLifeCycle">
     <Arg>
       <New class="org.mortbay.jetty.deployer.WebAppDeployer">
         <Set name="contexts"><Ref id="Contexts"/></Set>
         <Set name="webAppDir"><SystemProperty name="jetty.home" default="."/>/webapps</Set>
   ......
        <Set name="configurationClasses"><Ref id="plusConfig"/></Set>
       </New>
     </Arg>
   </Call>


Alternatively, one can turn it on in one particular webApp, but that is kind of complicated, and we would like resources configured in server level. So this works fine for us.

2. Adding the resources.
Create a xml file (myjndi.xml) and put it along with jetty.xml in jetty.home/etc.
<Configure  id="Server" class="org.mortbay.jetty.Server">
<New id="dataSource" class="org.mortbay.jetty.plus.naming.Resource">
        <Arg>jdbc/dataSource</Arg>
        <Arg>
            <New class="org.apache.commons.dbcp.BasicDataSource">
                <Set name="driverClassName">com.mysql.jdbc.Driver</Set>
                <Set name="url">jdbc:mysql://localhost:3306/diy090?autoReconnect=true</Set>
                <Set name="username">user</Set>
                <Set name="password">pass</Set>
                <Set name="maxActive">100</Set>
                <Set name="maxWait">1000</Set>
                <Set name="poolPreparedStatements">true</Set>
                <Set name="defaultAutoCommit">true</Set>
            </New>
        </Arg>
    </New>
    
    <New class="org.mortbay.jetty.plus.naming.EnvEntry">
        <Arg>flowservice/url</Arg>
        <Arg type="java.lang.String">rmi://remote.service.call:1099/FlowService</Arg>
        <Arg type="boolean">true</Arg>
    </New>
.......
</configure>

This will add the resource ("java:comp/env/jdbc/dataSource", "java:comp/env/flowservice/url") in the server level that available to every web applications. Note that "id" attribute in configure tag needs to match "id" attribute in jetty.xml so jetty know they are for the same server.
Now instead of starting jetty by "java -jar start.jar", we need to tell jetty to use both the default setting (etc/jetty.xml) and extra xml (myjndi.xml), the command is "java -jar start.jar etc/jetty.xml etc/myjndi.xml". Or we can simply put the content of <configure> tag into jetty.xml. Then one can use the same old command "java -jar start.jar".

Alternatively one can created a xml file called jetty-env.xml with the resource and put it along with web.xml in WEB-INF. This supplies the info only for that particular web application. But now one should use instead

<Configure class="org.mortbay.jetty.webapp.WebAppContext">


Note: to make the data source work, relevant jars need to be in jetty's library jetty.home/lib/ext. For common dbcp with mysql. Here is the list:
commons-dbcp-1.2.1.jar
commons-collections-3.2.jar
commons-pool-1.2.jar
mysql-connector-java-5.1.6.jar



3. Now the normal objects (String, "java:comp/env/flowservice/url") are exposed to web application now. But datasource is not yet. (That took me more than a day to figure out/realize.) One has to declare it in web.xml of web application.

<resource-ref>
<res-ref-name>jdbc/dataSource</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>


4. Spring provided the new tag that is much easier to work with than previously.

<jee:jndi-lookup jndi-name="java:comp/env/jdbc/dataSource" id="dataSource" />

<jee:jndi-lookup jndi-name="java:comp/env/flowservice/url" id="flowserviceUrl" />

That is it. Now you have two beans dataSource(java.sql.DataSource) and flowserviceUrl(String) ready for anything you likes.