I have a simple subflow working with Spring Web Flow 2.0. It also has success messages and validation controlled by the flow. The example builds on Simple Spring Web Flow Webapp by adding an address subflow to the person flow along with displaying a message resource after a successful save. Validation for the flow and subflow are handled automatically by Spring Web Flow. The person flow uses a bean, personValidator, named to match the flow. The address flow has a method, 'validateAddressForm(MessageContext context)', in the Address class that matches the id of the view-state it should be called for
The subflow was a little bit of work. It's pretty straightfoward, but I wasn't used to reading the error messages from Web Flow showing all the scope variables. Also, I couldn't find a specific example doing a subflow for Spring Web Flow 2.0. Basically the input needs to be configured in the subflow-state and then again at the beginning of the subflow, and the output needs to be configured the same way.
Excerpt from Person Flow
<subflow-state id="address" subflow="address">
<input name="id" value="addressId"/>
<input name="person" value="person"/>
<output name="address" />
<transition on="saveAddress" to="personForm">
<evaluate expression="personDao.saveAddress(id, address)" result="flowScope.person" />
<set name="flashScope.statusMessageKey" value="'address.form.msg.success'" />
</transition>
<transition on="cancelAddress" to="personForm" />
</subflow-state>
Excerpt from Address Subflow
<input name="id" />
<input name="person" />
...
<end-state id="saveAddress">
<output name="address" value="address"/>
</end-state>
Tuesday, June 24, 2008
Thursday, June 19, 2008
Spring Web Flow Request Exception
In the previous post on Spring Web Flow 2.0, I mentioned that I was getting a 'Could not complete request' error as an IllegalStateException at the end of the flow. I found the issue. It was because the URL in the flow handler wasn't a context relative one. I was returning 'person/search', but that was just being added to the end of the current URL. The correct value to return is 'contextRelative:/person/search.html' (the servlet-mapping for the DispatcherServlet is '*.html'). With this working, I was able to remove the view attribute from the end-state elements in the flow. See the Simple Spring Web Flow Webapp example for more information.
@Component
public class PersonFlowHandler extends AbstractFlowHandler {
/**
* Where the flow should go when it ends.
*/
@Override
public String handleExecutionOutcome(FlowExecutionOutcome outcome,
HttpServletRequest request,
HttpServletResponse response) {
return getContextRelativeUrl(PersonController.SEARCH_VIEW_KEY);
}
/**
* Where to redirect if there is an exception not handled by the flow.
*/
@Override
public String handleException(FlowException e,
HttpServletRequest request,
HttpServletResponse response) {
if (e instanceof NoSuchFlowExecutionException) {
return getContextRelativeUrl(PersonController.SEARCH_VIEW_KEY);
} else {
throw e;
}
}
/**
* Gets context relative url with an '.html' extension.
*/
private String getContextRelativeUrl(String view) {
return "contextRelative:" + view + ".html";
}
}
@Component
public class PersonFlowHandler extends AbstractFlowHandler {
/**
* Where the flow should go when it ends.
*/
@Override
public String handleExecutionOutcome(FlowExecutionOutcome outcome,
HttpServletRequest request,
HttpServletResponse response) {
return getContextRelativeUrl(PersonController.SEARCH_VIEW_KEY);
}
/**
* Where to redirect if there is an exception not handled by the flow.
*/
@Override
public String handleException(FlowException e,
HttpServletRequest request,
HttpServletResponse response) {
if (e instanceof NoSuchFlowExecutionException) {
return getContextRelativeUrl(PersonController.SEARCH_VIEW_KEY);
} else {
throw e;
}
}
/**
* Gets context relative url with an '.html' extension.
*/
private String getContextRelativeUrl(String view) {
return "contextRelative:" + view + ".html";
}
}
Tuesday, June 17, 2008
Simple Spring Web Flow 2.0 Example Ready
I finally have a very Simple Spring Web Flow 2.0 example ready. It's building on the Spring MVC annotation-based Controller and Spring Security 2.0 examples I did recently. I'm actually still working on a more advanced example showing how to have a subflow for Person addresses, but I wanted to post what was working so far and it might be less confusing to people as they're getting started since there is less going on.
There is an error currently when leaving the flow from a save and a cancel. It says 'Could not complete request' and is an IllegalStateException. It seems to be some problem with a redirect, but everything works fine.
The more advanced subflow example just needs to have messages and validation added to it., and I might try to switch it from using a Hibernate DAO class to using Spring Web Flow's built in JPA persistence hooks. It really took a long time to get the subflow working. I had to try a lot of different things to get the address id passed into the subflow. Spring Web Flow is really nice, but there could be a lot more documentation and examples illustrating things like this. I couldn't find any concrete examples doing this for Spring Web Flow 2.0.
The person flow uses Spring Security to limit the flow to a 'ROLE_USER'. Based on whether or not an id attribute is available, the decision-state element will forward to a 'createPerson' or 'editPerson' action-state. The 'createPerson' one uses El to create a new Person instance for the form to bind to using a method on the person controller. The edit uses the person DAO instance to look up the person record. They both forward to the 'personForm' where you can save or cancel, and save uses the person DAO to save the person instance. The person instance is automatically bound by specifying the model attribute as 'person' on the view-state. Both cancel and save then populate the latest search results and forward to the search page.
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<secured attributes="ROLE_USER" />
<input name="id" />
<decision-state id="createOrEdit">
<if test="id == null" then="createPerson" else="editPerson" />
</decision-state>
<action-state id="createPerson">
<evaluate expression="personController.newPerson()"
result="flowScope.person" />
<transition to="personForm" />
</action-state>
<action-state id="editPerson">
<evaluate expression="personDao.findPersonById(id)"
result="flowScope.person" />
<transition to="personForm" />
</action-state>
<view-state id="personForm" model="person" view="/person/form">
<transition on="save" to="savePerson">
<evaluate expression="personDao.save(person)" />
<evaluate expression="personDao.findPersons()"
result="flowScope.persons" />
</transition>
<transition on="cancel" to="cancelPerson" bind="false">
<evaluate expression="personDao.findPersons()"
result="flowScope.persons" />
</transition>
</view-state>
<end-state id="savePerson" view="/person/search" />
<end-state id="cancelPerson" view="/person/search" />
</flow>
There is an error currently when leaving the flow from a save and a cancel. It says 'Could not complete request' and is an IllegalStateException. It seems to be some problem with a redirect, but everything works fine.
The more advanced subflow example just needs to have messages and validation added to it., and I might try to switch it from using a Hibernate DAO class to using Spring Web Flow's built in JPA persistence hooks. It really took a long time to get the subflow working. I had to try a lot of different things to get the address id passed into the subflow. Spring Web Flow is really nice, but there could be a lot more documentation and examples illustrating things like this. I couldn't find any concrete examples doing this for Spring Web Flow 2.0.
The person flow uses Spring Security to limit the flow to a 'ROLE_USER'. Based on whether or not an id attribute is available, the decision-state element will forward to a 'createPerson' or 'editPerson' action-state. The 'createPerson' one uses El to create a new Person instance for the form to bind to using a method on the person controller. The edit uses the person DAO instance to look up the person record. They both forward to the 'personForm' where you can save or cancel, and save uses the person DAO to save the person instance. The person instance is automatically bound by specifying the model attribute as 'person' on the view-state. Both cancel and save then populate the latest search results and forward to the search page.
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<secured attributes="ROLE_USER" />
<input name="id" />
<decision-state id="createOrEdit">
<if test="id == null" then="createPerson" else="editPerson" />
</decision-state>
<action-state id="createPerson">
<evaluate expression="personController.newPerson()"
result="flowScope.person" />
<transition to="personForm" />
</action-state>
<action-state id="editPerson">
<evaluate expression="personDao.findPersonById(id)"
result="flowScope.person" />
<transition to="personForm" />
</action-state>
<view-state id="personForm" model="person" view="/person/form">
<transition on="save" to="savePerson">
<evaluate expression="personDao.save(person)" />
<evaluate expression="personDao.findPersons()"
result="flowScope.persons" />
</transition>
<transition on="cancel" to="cancelPerson" bind="false">
<evaluate expression="personDao.findPersons()"
result="flowScope.persons" />
</transition>
</view-state>
<end-state id="savePerson" view="/person/search" />
<end-state id="cancelPerson" view="/person/search" />
</flow>
Labels:
spring by example,
spring framework,
spring web flow
Monday, June 9, 2008
Using Spring Security to Secure a Webapp
I had to go to a few different places to get all this working, but I have simple security on a webapp that secures the URLs and a method on a DAO interface. Besides securing the delete method in the DAO interface, the link is also hidden from non-admin users. I have this working with JDBC authentication and also with static users defined in the XML (which are commented out, but still in the XML configuration).
Basically you just need to define a security filter, configure Spring Security in an XML file, secure methods either with pointcuts in the XML or with annotations.
Secure DAO Method
Either secure with an annotation-based config, as in the example posted.
<security:global-method-security secured-annotations="enabled" />
/**
* Deletes person.
*/
@Secured ({"ROLE_ADMIN"})
public void delete(Person person);
Or as a pointcut in the XML configuration.
<security:global-method-security>
<!-- Any delete method in a class ending in 'Dao' in the 'org.springbyexample.orm.hibernate3.annotation.dao' package. -->
<security:protect-pointcut
expression="execution(* org.springbyexample.orm.hibernate3.annotation.dao.*Dao.delete(..))"
access="ROLE_ADMIN"/>
</security:global-method-security>
In Spring Security Config
<security:authentication-provider>
<security:jdbc-user-service data-source-ref="dataSource" />
</security:authentication-provider>
SQL Script
Script works for HSQLDB and based on Spring Security Script. The ACL tables aren't defined because they aren't used at all in this example.
SET IGNORECASE TRUE;
CREATE TABLE users (
username VARCHAR(50) NOT NULL PRIMARY KEY,
password VARCHAR(50) NOT NULL,
enabled BIT NOT NULL
);
CREATE TABLE authorities (
username VARCHAR(50) NOT NULL,
authority VARCHAR(50) NOT NULL
);
CREATE UNIQUE INDEX ix_auth_username ON authorities (username, authority);
ALTER TABLE authorities ADD CONSTRAINT fk_authorities_users foreign key (username) REFERENCES users(username);
INSERT INTO users VALUES ('david', 'newyork', true);
INSERT INTO users VALUES ('alex', 'newjersey', true);
INSERT INTO users VALUES ('tim', 'illinois', true);
INSERT INTO authorities VALUES ('david', 'ROLE_USER');
INSERT INTO authorities VALUES ('david', 'ROLE_ADMIN');
INSERT INTO authorities VALUES ('alex', 'ROLE_USER');
INSERT INTO authorities VALUES ('tim', 'ROLE_USER');
Basically you just need to define a security filter, configure Spring Security in an XML file, secure methods either with pointcuts in the XML or with annotations.
Secure DAO Method
Either secure with an annotation-based config, as in the example posted.
<security:global-method-security secured-annotations="enabled" />
/**
* Deletes person.
*/
@Secured ({"ROLE_ADMIN"})
public void delete(Person person);
Or as a pointcut in the XML configuration.
<security:global-method-security>
<!-- Any delete method in a class ending in 'Dao' in the 'org.springbyexample.orm.hibernate3.annotation.dao' package. -->
<security:protect-pointcut
expression="execution(* org.springbyexample.orm.hibernate3.annotation.dao.*Dao.delete(..))"
access="ROLE_ADMIN"/>
</security:global-method-security>
In Spring Security Config
<security:authentication-provider>
<security:jdbc-user-service data-source-ref="dataSource" />
</security:authentication-provider>
SQL Script
Script works for HSQLDB and based on Spring Security Script. The ACL tables aren't defined because they aren't used at all in this example.
SET IGNORECASE TRUE;
CREATE TABLE users (
username VARCHAR(50) NOT NULL PRIMARY KEY,
password VARCHAR(50) NOT NULL,
enabled BIT NOT NULL
);
CREATE TABLE authorities (
username VARCHAR(50) NOT NULL,
authority VARCHAR(50) NOT NULL
);
CREATE UNIQUE INDEX ix_auth_username ON authorities (username, authority);
ALTER TABLE authorities ADD CONSTRAINT fk_authorities_users foreign key (username) REFERENCES users(username);
INSERT INTO users VALUES ('david', 'newyork', true);
INSERT INTO users VALUES ('alex', 'newjersey', true);
INSERT INTO users VALUES ('tim', 'illinois', true);
INSERT INTO authorities VALUES ('david', 'ROLE_USER');
INSERT INTO authorities VALUES ('david', 'ROLE_ADMIN');
INSERT INTO authorities VALUES ('alex', 'ROLE_USER');
INSERT INTO authorities VALUES ('tim', 'ROLE_USER');
Labels:
spring by example,
spring framework,
spring security
Sunday, June 8, 2008
Unit Testing AspectJ Load-time Weaving with Maven
This came up as a question on the Spring forums and I actually spent a bit of time trying to figure out how to get the unit test working properly when I did this originally. To unit test AspectJ Load-time Weaving (LTW), the surefire plugin needs to be configured to take the LTW agent. Which in this example is the Spring Agent. Also, the test needs to be run in it's own JVM so the javaagent argument can be applied.
The dependency on spring-agent can have it's scope set to 'provided'. It isn't needed as part of the build since it's used as a command line argument and Maven still downloads the jar so it can be referenced for the test in the local Maven repository.
Note: The argLine element should all be together on one line, but was separated to display in the blog entry.
<properties>
<spring.version>2.5.4</spring.version>
</properties>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-agent</artifactId>
<version>${spring.version}</version>
<scope>provided</scope>
</dependency>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.4</version>
<configuration>
<forkMode>once</forkMode>
<argLine>
-javaagent:${settings.localRepository}
/org/springframework/spring-agent/${spring.version}/
spring-agent-${spring.version}.jar
</argLine>
<useSystemClassloader>true</useSystemClassloader>
</configuration>
</plugin>
Labels:
aop,
aspectj,
ltw,
maven,
spring by example,
spring framework
Friday, June 6, 2008
Maven Internationalized Compilation Issue
I've never had this come up before, but as I was working on a security that was using the internationalization (i18n) from the Basic Webapp Internationalization I decided to add actually inserting a UTF-8 Korean name into the database. This ended up revealing a bug in the
Spring by Example JDBC Module. It wasn't correctly setting the encoding type when reading in a script file. This was easy enough to fix and I made a 1.0.2 release with that and a few other changes including better unit testing for UTF-8 scripts.
I had everything working in Eclipse, but I couldn't successfully run the tests from the command line. I spent quite a while on this and finally realized it was the actual compilation step that was causing a problem. It took a long time because debugging in Eclipse, everything was fine.
The solution to the problem was to explicitly add the character encoding type to the Maven compiler plugin. Eclipse was automatically correctly setting the encoding. After doing the internationalization examples, it seems like everything is still stuck the way it was 7-10 years ago for the most part. You would think that everything should default to the way Java stores a java.lang.String (UTF-16).
<plugin>
<artifactid>maven-compiler-plugin</artifactid>
<configuration>
<source>1.5</source>
<target>1.5</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
Spring by Example JDBC Module. It wasn't correctly setting the encoding type when reading in a script file. This was easy enough to fix and I made a 1.0.2 release with that and a few other changes including better unit testing for UTF-8 scripts.
I had everything working in Eclipse, but I couldn't successfully run the tests from the command line. I spent quite a while on this and finally realized it was the actual compilation step that was causing a problem. It took a long time because debugging in Eclipse, everything was fine.
The solution to the problem was to explicitly add the character encoding type to the Maven compiler plugin. Eclipse was automatically correctly setting the encoding. After doing the internationalization examples, it seems like everything is still stuck the way it was 7-10 years ago for the most part. You would think that everything should default to the way Java stores a java.lang.String (UTF-16).
<plugin>
<artifactid>maven-compiler-plugin</artifactid>
<configuration>
<source>1.5</source>
<target>1.5</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
Subscribe to:
Posts (Atom)