jetty maven version 7 beta woes

So I gave the beta of the jetty maven plugin a try.

What I had was this:

1
2
3
<groupId>org.mortbay.jetty</groupId>
<artifactId>maven-jetty-plugin</artifactId>
<version>6.1.19</version>

I changed it to this which is the most current available version in the repos:

1
2
<artifactId>jetty-maven-plugin</artifactId>
<version>7.0.0.1beta3</version>

N.B. for maximum cognitive dissonance the jetty guys thought it would be a good idea to swap some words in the name around. That’s not a typo.

Well it started.  Six count ‘em six minutes later. I made a change to my Java source and was welcomed with this:

Unable to locate Spring NamespaceHandler for XML schema namespace etc. etc.

And then jetty cratered and had to be killed then restarted. Hardly “agile” with a 6 minute start up time and all you want to do is save one Java file…

So I went through the usual: was the spring jar in question on the classpath? Was the spelling of the namespace in the XML config file correct? Was the maven assembly plugin doing bad things? Was etc. etc.etc.

The answer is NO. The problem is the plugin.

Back to version 6 for me.

Posted in Maven, Spring Framework | Tagged , , | 1 Response

Spring, BasicDataSource and OpenJPA

So you want to use Spring with commons BasicDataSource and OpenJPA? There is an interesting gotcha.

Let’s start with just the DriverManager DataSource for comparison. In your spring context you define the datasource like this:

1
2
3
4
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close" p:driverClassName="org.hsqldb.jdbcDriver"
		p:url="jdbc:hsqldb:hsql://localhost:9001/testdb" p:username="sa"
		p:password="" />

Then your EntityManager Factory:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="persistenceUnitName" value="jpamaven-test" />
<property name="loadTimeWeaver">
<bean
class="org.springframework.instrument.classloading.SimpleLoadTimeWeaver" />
</property>
 
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter">
<property name="showSql" value="true" />
<property name="generateDdl" value="true" />
<property name="database" value="HSQL" />
<property name="databasePlatform" value="org.apache.openjpa.jdbc.sql.HSQLDictionary" />
</bean>
</property>
<property name="jpaPropertyMap">
<map>
<entry key="openjpa.ConnectionDriverName" value="org.hsqldb.jdbcDriver" />
<entry key="openjpa.ConnectionURL" value="jdbc:hsqldb:hsql://localhost:9001/testdb" />
<entry key="openjpa.ConnectionUserName" value="sa" />
<entry key="openjpa.ConnectionPassword" value="" />
 
<entry key="openjpa.jdbc.SynchronizeMappings" value="buildSchema(SchemaAction='add', ForeignKeys=true)" />
<entry key="openjpa.jdbc.DBDictionary" value="hsql" />
<entry key="openjpa.jdbc.Schema" value="PUBLIC" />
<entry key="openjpa.Log"
value="DefaultLevel=TRACE, Runtime=TRACE, Tool=TRACE, SQL=TRACE" />
<entry key="openjpa.ConnectionFactoryProperties" value="PrettyPrint=true, PrettyPrintLineLength=72" />
</map>
</property>
<property name="jpaDialect" ref="jpaDialect" />
</bean>

Here META-INF/persistence.xml is almost empty because all the properties are specified here in Spring. See the Spring docs for more info.

The trouble starts when you change your datasource to this:

1
2
3
4
5
6
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
   destroy-method="close" 
   p:driverClassName="org.hsqldb.jdbcDriver"
   p:url="jdbc:hsqldb:hsql://localhost:9001/testdb" 
   p:username="sa"
   p:password="" />

It “should” just work. No such luck. To make a long story short you have to change the openjpa properties.

Here is the combination that works.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<property name="jpaPropertyMap">
   <map>
      <entry key="openjpa.ConnectionDriverName" value="org.apache.commons.dbcp.BasicDataSource" />
      <entry key="openjpa.ConnectionProperties"
      value="DriverClassName=org.hsqldb.jdbcDriver,Url=jdbc:hsqldb:hsql://localhost:9001/testdb,Username=sa,Password=" />
 
      <entry key="openjpa.jdbc.SynchronizeMappings" value="buildSchema(SchemaAction='add', ForeignKeys=true)" />
      <entry key="openjpa.jdbc.DBDictionary" value="hsql" />
      <entry key="openjpa.jdbc.Schema" value="PUBLIC" />
      <entry key="openjpa.Log"
         value="DefaultLevel=TRACE, Runtime=TRACE, Tool=TRACE, SQL=TRACE" />
      <entry key="openjpa.ConnectionFactoryProperties" value="PrettyPrint=true, PrettyPrintLineLength=72" />
   </map>
</property>

You have to gang up four properties into openjpa.ConnectionProperties. Intuitive huh?

Posted in JPA, Spring Framework | Tagged , , , | Leave a comment

Spring Test Framework CoC suggestion

Let’s say you’re using JUnit 4 and the Spring Testing classes (the 2.5+ versions) for your unit tests. You can specify parameters as shown in the TransactionConfiguration annotation as shown in this example. But you can also get away with leaving them out in a nod to the uberhip Convention Over Configuration gods as shown in ContextConfigurations

1
2
3
4
5
6
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
@Transactional
public class UserServiceTest extends
AbstractTransactionalJUnit4SpringContextTests {

So here the context will be loaded by classname-context.xml (e.g. UserServiceTest-context.xml) in the same package as the test class. Cool! You can save your precious digits from typing a few characters.

Let’s say you have unit tests for the persistence layer too. So you have another config file named UserDAOTest-context.xml. And so on for all your other domain classes. Lots of config files that change only a little.

Maybe one has

1
2
<aop:advisor pointcut="execution(* *..Service+.*(..))"
advice-ref="txAdvice" />

and another has

1
2
<aop:advisor pointcut="execution(* *..DAO+.*(..))"
advice-ref="txAdvice" />

You’re also all wet. There is nothing DRY about this. You are repeating many things with the possibility of missing one. Case in point:
I decided to try to define the datasource like this:

1
2
3
4
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close" p:driverClassName="org.hsqldb.jdbcDriver"
p:url="jdbc:hsqldb:hsql://localhost:9001/testdb" p:username="sa"
p:password="" />

instead of like this:

1
2
3
4
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource"
p:driverClassName="org.hsqldb.jdbcDriver" p:url="jdbc:hsqldb:hsql://localhost:9001/testdb"
p:username="sa" p:password="" />

Small trivial thing right? Except that I missed changing it in one of the config files. Well, what happens to the transaction rollback? Since I’m using AbstractTransactionalJUnit4SpringContextTests I want the junk I shoved into the DB in the @Before method (within the transaction) to be rolled back after the test method. Nope. Ain’t gonna happen. Do a check in @BeforeTransaction and verify in @AfterTransaction and you’ll see. But first you’ll waste a few hours tracking it down thinking the real problem is in one of those Spring XML/Annotation hell holes.

But what would happen if you decided to be unhip and say screw CoC in this case (avoiding a CoC up)?
Something like this:

1
@ContextConfiguration(locations = { "classpath:com/rockhoppertech/example/test-context.xml" })

OMG you had to type more. And that’s so declasse!
But then you wouldn’t have problems like I just described and you wouldn’t be repeating yourself all over the place like rainman.

Posted in Spring Framework, Testing | Tagged , , | 1 Response

Multiple Persistence Units – Websphere v7, OpenJPA

Want to do something exotic like specifying two persistence units in your persistence.xml file? Perhaps one local and one JTA managed. Maybe even go whole hog and do that to write unit tests?

In Websphere v7 (RAD 7.5) when you have multiple PUs in persistence.xml it barfs like this:

[7/8/09 10:01:55:149 EDT] 00000053 servlet       I com.ibm.ws.webcontainer.servlet.ServletWrapper init SRVE0242I: [someAppEAR] [/someAppJPAWeb] [/someAppUsersTable.jsp]: Initialization successful.
[7/8/09 10:01:55:227 EDT] 00000053 JPAApplInfo   E   CWWJP0012E: The persistence unit name is not specified and a unique persistence unit is not found in the someAppEAR application and someAppJPAWeb.war module.
[7/8/09 10:01:55:227 EDT] 00000053 JPAApplInfo   E   CWWJP0029E: The server cannot find the "" persistence unit in the someAppJPAWeb.war module and the someAppEAR application.
[7/8/09 10:01:55:227 EDT] 00000053 InjectionBind E   CWNEN0035E: The someAppJPAWeb reference of type javax.persistence.EntityManagerFactory for the <null> component in the someAppJPAWeb.war module of the someAppEAR application cannot be resolved.
[7/8/09 10:01:55:242 EDT] 00000053 annotation    E com.ibm.ws.webcontainer.annotation.WASAnnotationHelper inject exception while injecting resource

Here is IBM’s “help”:

http://publib.boulder.ibm.com/infocenter/dmndhelp/v6r2mx/index.jsp?topic=com.ibm.websphere.wbpm.messages.620.doc/messages/com.ibm.ws.jpa.jpa.html

CWWJP0012E: The persistence unit name is not specified and a unique persistence unit is not found in the {0} application and {1} module.
Problem    The application has defined a persistence unit or persistence context reference without a persistence unit name and a unique persistence unit cannot be found.
User response    Change the application to specify a persistence unit name for the reference, or provide a unique persistence.

Yeah, huh? What’s that mean? A reference in web.xml? and what name for it?
Can’t find the official answer anywhere. Let’s waste time searching and looking at logs and enjoying the wonderful WAS admin console.

OK, here is my answer.
Let’s say the JTA PU you want to use in your web app is named

1
2
<persistence-unit name="someAppJPAWeb"  transaction-type="JTA">
etc.

Then in web.xml create a PU reference:

1
2
3
4
5
6
7
<persistence-unit-ref>
   <description>
      Persistence unit for the someApp application.
   </description>
   <persistence-unit-ref-name>persistence/someAppJPAWeb</persistence-unit-ref-name>
   <persistence-unit-name>someAppJPAWeb</persistence-unit-name>
</persistence-unit-ref>

In your injected class use the reference name:

1
2
@PersistenceUnit(name="persistence/someAppJPAWeb")
private EntityManagerFactory emf;

If you have just one PU then you don’t need the name attribute nor the persistence-unit-ref.

Of course when you are looking at persistence.xml RAD will helpfully tell you that it doesn’t like multiple persistence units.

Posted in JPA, Websphere | Tagged , , | Leave a comment

JSF 2.0

JSF 2.0 is out.

But there is still no maven love here: https://maven-repository.dev.java.net/nonav/repository/javax.faces/jars/

But over at JBoss we have something
close:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependency>
 
   <groupId>javax.faces</groupId>
 
   <artifactId>jsf-api</artifactId>
 
   <version>2.0.0-PR2_3</version>
 
   </dependency>
 
   <dependency>
 
      <groupId>javax.faces</groupId>
 
      <artifactId>jsf-impl</artifactId>
 
      <version>2.0.0-PR2_3</version>
 
   </dependency>

is in this repository:

1
2
3
4
5
6
7
8
9
<repository>
 
   <id>maven.jboss.com</id>
 
   <name>jboss Repository for Maven</name>
 
   <url>http://repository.jboss.org/maven2</url>
 
</repository>

Looks like the ManagedBean annotation does not work though. So I tried it with the mojarra jars and did the mvn install:file chacha. Still no go.

So get the source and build the jars yourself.

Update:

After I published the above they pushed a snapshot to the repo. Take a look here:

They are using a different groupId so do this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependency>
 
   <groupId>com.sun.faces</groupId>
 
   <artifactId>jsf-api</artifactId>
 
   <version>2.0.0-SNAPSHOT</version>
 
</dependency>
 
<dependency>
 
   <groupId>com.sun.faces</groupId>
 
   <artifactId>jsf-impl</artifactId>
 
   <version>2.0.0-SNAPSHOT</version>
 
</dependency>

Verdict: It still fails the simplest @ManagedBean example.

But I got it to finally work by doing a jetty:run-war instead of jetty:run.

Posted in JSF, Maven | Tagged | 1 Response

Oracle buys Sun

It’s official. Oracle will buy Sun.

Posted in Uncategorized | Tagged | Leave a comment

Maven profiles : filtering for hsqldb and MySQL

Many developers like to use a lightweight embedded database such as Hsqldb or Derby in their development environment. But usually the application is deployed using another database such as MySQL – or Oracle, DB2 etc. Maven profiles are a way to segregate resources for each environment and to switch easily between them.

The Spring Framework allows you to put environment specific settings in Java properties files. You can read them in to your application context with a single line. First for convenience add the p namespace to your config file.

<context:property-placeholder location="classpath:testdb.properties" />

The testdb.properties file can contain actual values for each key. Instead we are going to use placeholders that will be expanded by maven.

testdb.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# this is filtered by maven
jdbc.driverClassName=${jdbc.driverClassName}
jdbc.url=${jdbc.url}
jdbc.username=${jdbc.username}
jdbc.password=${jdbc.password}
 
jpaVendorAdapter.showSql=${jpaVendorAdapter.showSql}
jpaVendorAdapter.generateDdl=${jpaVendorAdapter.generateDdl}
jpaVendorAdapter.database=${jpaVendorAdapter.database}
jpaVendorAdapter.databasePlatform=${jpaVendorAdapter.databasePlatform}
 
# set in spring context jpaProperties instead of persistence.xml
hibernate.dialect=${hibernate.dialect}
hibernate.show_sql=${hibernate.show_sql}
hibernate.format_sql=${hibernate.format_sql}
hibernate.jdbc.batch_size=${hibernate.jdbc.batch_size}
hibernate.hbm2ddl.auto=${hibernate.hbm2ddl.auto}
 
persistence.xml=${persistence.xml}

Here is another properties file with actual values. Maven will filter each of the placeholders in testdb.propeties with the value for each matching key in this file. The maven profile in use triggers this.

hibernatejpa-mysql.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/composerdb
jdbc.username=root
jdbc.password=penguin69
 
jpaVendorAdapter.showSql=true
jpaVendorAdapter.generateDdl=true
jpaVendorAdapter.database=MYSQL
jpaVendorAdapter.databasePlatform=org.hibernate.dialect.MySQL5InnoDBDialect
 
# set in spring context jpaProperties instead of persistence.xml
hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
hibernate.show_sql=true
hibernate.format_sql=true
hibernate.jdbc.batch_size=100
hibernate.hbm2ddl.auto=update
 
persistence.xml=META-INF/test-hibernate-persistence.xml

In your pom.xml you define your profiles. You set up environment specific dependencies within your profile. Then you can specify which resource folders are filtered, those that are not and what file contains the actual values.
Here is a profile for MySQL that enables the filtering.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<profile>
	<id>hibernatejpa-mysql</id>
	<activation>
		<activeByDefault>true</activeByDefault>
	</activation>
 
	<dependencies>
		<dependency>
			<groupId>org.hibernate.java-persistence</groupId>
			<artifactId>jpa-api</artifactId>
			<version>2.0-cr-1</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>3.5.0-Beta-2</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.5.2</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.5.2</version>
		</dependency>
		<dependency>
			<groupId>jboss</groupId>
			<artifactId>jboss-archive-browsing</artifactId>
			<version>5.0.0alpha-200607201-119</version>
		</dependency>
	</dependencies>
 
	<build>
		<resources>
			<resource>
				<directory>src/main/hibernate</directory>
			</resource>
			<resource>
				<directory>src/test/hibernate</directory>
				<filtering>false</filtering>
				<includes>
					<include>**/**/test-context.xml</include>
				</includes>
				<targetPath>${project.build.directory}/test-classes</targetPath>
			</resource>
 
			<resource>
				<directory>src/test/resources/</directory>
				<filtering>true</filtering>
 
				<includes>
					<include>**/*.properties</include>
				</includes>
 
				<excludes>
					<exclude>**/*.sql</exclude>
				</excludes>
 
				<targetPath>${project.build.directory}/test-classes</targetPath>
			</resource>
		</resources>
 
		<!-- project.build.directory is ${basedir}/target -->
 
		<filters>
			<filter>${basedir}/src/main/filters/hibernatejpa-mysql.properties</filter>
		</filters>
 
	</build>
</profile>

Resources

Posted in Maven, Spring Framework | Tagged | Leave a comment

Jersey 1.0 and jetty:run

If you are using maven and jetty you are probably simply invoking jetty:run with your Jersey web service.

Jersey is the JAX-RS (JSR 311) Reference Implementation for building RESTful Web services

The problem is you get this exception:

The ResourceConfig instance does not contain any root resource classes.

But you are sure you do have your resources. Here is an example:

1
2
3
4
5
6
7
8
@Path("/rocky/")
public class RockyResource {
  @GET
  @Produces("text/plain")
  public String yo() {
    return "Yo Adrian!";
  }
}

In pre 1.0 releases you used the annotations @ProduceMime(“text/plain”), @UriTemplate(“/rocky/”) and @HttpMethod(“GET”). These are the replacements for 1.0.

You think that Rest is so hip and happenin’ that other people have had the same problem so you google the exception.

You then find this blog post. It was helpful but that was a preview release.

Here is a web.xml that will now work:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="2.5"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
 
<display-name>Jersey test</display-name>
 
<servlet>
  <servlet-name>ServletContainer</servlet-name>
  <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
  <init-param>
    <param-name>com.sun.jersey.config.property.packages</param-name>
    <!-- multiple packages separated by ; -->
      <param-value>com.rockhoppertech.jerseyServer</param-value>
    </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
 
<servlet-mapping>
  <servlet-name>ServletContainer</servlet-name>
  <url-pattern>/resources/*</url-pattern>
</servlet-mapping>
 
</web-app>

See the Glassfish Wiki for details.

Here are the javadocs for the JSR. Since I’m posting links, if you are new to JSON here are some docs.

If you created your maven app with the Jersey teams maven archetype then this wouldn’t have been a problem. You’d just have to delete stuff from the pom and insert the jetty stuff.

 mvn archetype:generate -DarchetypeCatalog=http://download.java.net/maven/2
Posted in REST, SOA | Tagged , | Leave a comment

Swing Drag and Drop on non supported components

The 1.4 release of Java SE introduced a much needed overhaul for the Drag and Drop subsystem. I sweated blood back in 1999 to make it reasonably work for a client’s requirements. That led to an hour long 1999 JavaOne talk (that was before the marketing crap seeped into JavaOne) and the now out of date DnD FAQ.

Many components such as JList, JTextField and others now have DnD baked into them. You call setDragEnabled(true) and provide them with a TransferHandler. The more complicated components like JTable have lots of customization possible through new support classes.

But what about the non-supported components like a JLabel? Or even your own components?

There are naiive approaches to adding drag capabilities to a non-supported component.  This article actually rips off a Sun DnD document without attribution (like the rest of the content on that site).  Here the drag is initiated on a mouse pressed event. Every other drag in the known universe is commenced after the mouse moves a bit. Additionally if you start the drag on a mouse press then you cannot notify listeners for any other mouse event since DnD takes over.

The real way it’s done is in the plaf layer. But maybe you just want a simple subclass of JComponent and don’t want to eat the whole MVC Swing elephant.

Take a look at Shannon Hickey’s excellent DnD contributions to the basic plaf. For recognizing the drag gesture take a look at javax.swing.plaf.basic.DragRecognitionSupport.

I borrowed some of his code for a simple JComponent subclass. First I enabled the events in the constructor like this.

1
this.enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);

Then I added two instance variables and overrode the event processing methods. On mouse press I simply saved (into those two instance variables) the mouse event and retrieved the number of pixels that the user needs to move the mouse in order to initiate the drag. On mouse release I null out the event (and in the example do other non DnD related stuff).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private int motionThreshold = 5;
private MouseEvent dndArmedEvent;
 
@Override
protected void processMouseEvent(MouseEvent e) {
  switch (e.getID()) {
  case MouseEvent.MOUSE_PRESSED:
    this.dndArmedEvent = e;
    this.motionThreshold = DragSource.getDragThreshold();
    break;
  case MouseEvent.MOUSE_RELEASED:
    if (this.isInside) // non DnD stuff
      this.notifyListeners();
    this.dndArmedEvent = null;
    break;
 
etc.

Then to actually initiate the drag the mouse dragged event checks the distance the mouse has been dragged and then begins if the distance exceeds the threshold. This is pretty much what Shannon does in the plaf – but he does a lot more checking. Take a look at his code for those checks.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
protected void processMouseMotionEvent(MouseEvent me) {
    switch (me.getID()) {
    case MouseEvent.MOUSE_DRAGGED:
      int dx = Math.abs(me.getX() - dndArmedEvent.getX());
      int dy = Math.abs(me.getY() - dndArmedEvent.getY());
      if ((dx > motionThreshold) || (dy > motionThreshold)) {
        TransferHandler th = this.getTransferHandler();
                                   th.exportAsDrag(this,
                                   me,
                                   TransferHandler.COPY);
     }
     break;
  }
}
Posted in GUI | Tagged , | Leave a comment

Spring autowiring Struts 1 Actions

That’s right. Struts 1. Not hip. Not happenin’. But in the real world you might have bills to pay and a pair-programmer to feed.

The docs are a bit sketchy with Spring 2.5 stuff.

First you set up your service layer in web.xml

1
2
3
4
5
6
7
8
9
<context-param>
   <description>Used by ContextLoaderListener</description>
   <param-name>contextConfigLocation</param-name>
   <param-value>/WEB-INF/spring-service-layer.xml</param-value>
</context-param>
<listener>
   <description>spring. looks for /WEB-INF/applicationContext.xml unless you set contextConfigLocation (see above)</description>
   <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

Inside my spring-service-layer.xml file I tell spring to look for classes annotated with one of the 2.5 @Component stereotypes – which include the @Service annotation.

1
2
3
4
<context:annotation-config />
   <context:component-scan base-package="com.rockhoppertech.example"
      scoped-proxy="targetClass">
</context:component-scan>

Here’s a simple service:

1
2
3
4
5
6
7
8
9
@Service("HelloService")
public class DefaultHelloService implements HelloService {
/*
* @see com.rockhoppertech.example.service.HelloService#getMessage()
*/
public String getMessage() {
   return "Yo";
}
}

Spring will create a root web context in application scope accessible with the attribute name org.springframework.web.context.WebApplicationContext.ROOT. My service appears here.

1
2
3
4
5
org.springframework.web.context.support.XmlWebApplicationContext@74907490: display name [Root WebApplicationContext]
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
HelloService

The Spring docs say one approach is to configure Spring to manage your Actions as beans, using the ContextLoaderPlugin which will read the appropriately specified spring config files like this:

1
2
3
4
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
   <set-property property="contextConfigLocation"
      value="/WEB-INF/action-servlet.xml"/>
</plug-in>

Then in action-servlet.xml for example you configure your actions as beans.

1
2
<bean name="/hello" class="com.rockhoppertech.example.struts.action.HelloAction">
</bean>

Then in struts-config.xml you specify a special Spring controller:

1
2
3
4
<controller>
   <set-property property="processorClass"      
      value="org.springframework.web.struts.DelegatingRequestProcessor"/>
</controller>

Also in struts-config.xml you have to change your actions definitions to use a Spring delegate. Notice
that the path matches the bean name specified in action-servlet.xml.

1
2
<action name="HelloForm" path="/hello"
   type="org.springframework.web.struts.DelegatingActionProxy">

Ok, that works fine. Not too dry though since I have to specify the bean twice.

Let’s try a newer Spring class.

In struts-config.xml I change the controller to this.

1
2
3
<controller>
<set-property   
   property="processorClass"value="org.springframework.web.struts.AutowiringRequestProcessor" /></controller>

Since it’s named AutowiringRequestProcessor I’ve used the @Autowire annotation in my Action to get the service injected (which will NOT work if you annotate the instance variable and not a setter method).

The docs say that you specify just the usual action tag in struts-config.xml.
But how will the action beans then be created?
I simply annotated HelloAction with @Component. The scan for the service found the action too. The thing is, they live in the parent web context not in the child context created by the contextLoader plugin:

(accessible as an application scoped attribute named org.springframework.web.struts.ContextLoaderPlugIn.CONTEXT)
org.springframework.web.context.support.XmlWebApplicationContext@4e8a4e8a: display name [WebApplicationContext for namespace 'action-servlet']

The beans you specify in your plugin config file live here. But since we’re doing the component scan chacha they aren’t living there anymore. Do you think that’s a bad thing?

Here’s my annotated Action:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Component
public class HelloAction extends Action {
private Logger logger = Logger.getLogger(HelloAction.class);
 
private HelloService helloService;
 
/**
* @param helloService
*            the helloService to set
*/
@Autowired(required = true)
public void setHelloService(HelloService helloService) {
   this.helloService = helloService;
   if (logger.isDebugEnabled()) {
      logger.debug("Service was set");
   }
}
 
@Override
public ActionForward execute(ActionMapping mapping, ActionForm form,
   HttpServletRequest request, HttpServletResponse response)
   throws Exception {
   ActionMessages errors = new ActionMessages();
   ActionForward forward = null;
 
   if (helloService == null) {
      errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
        "helloservice.null"));
      if (logger.isDebugEnabled()) {
         logger.debug("Hello service is null");
      }
   } else {
      request.setAttribute("Greeting", helloService.getMessage());
      if (logger.isDebugEnabled()) {
         logger.debug("Set request attribute Greeting");
      }
   }
   if (!errors.isEmpty()) {
      saveErrors(request, errors);
      forward = mapping.findForward("failure");
   } else {
      forward = mapping.findForward("success");
   }
   return forward;
}

Here is a mavenized project with all the sources for this post.

Posted in Spring Framework, Web | Tagged , | 3 Responses