Search posterous

Search all posts and users. Type a name, type a favorite song title, whatever! See what comes up.
  

More posterous blogs











More recommended blogs »

Here are posterous posts filed under spring...

trapo says...

Sometime ago I was fighting with Hibernate Validator, trying to implement an unique constraint and I could not figure out how to create one that requires database access:

The problem is that your validators can't access sessionFactory or any other interface to the database. I know that I can break some rules and instantiate a sessionFactory/session/connection directly inside the Validator, but I dislike to do wrong things consciously. So, I looking for alternatives.

Then, while studying the new validation support offered by Spring, I just discovery that using Spring I can inject any bean inside my ConstraintValidators. From Spring documentation:

By default, the LocalValidatorFactoryBean configures a SpringConstraintValidatorFactory that uses Spring to create ConstraintValidator instances. This allows your custom ConstraintValidators to benefit from dependency injection like any other Spring bean.

And here is how you can use that Spring feature to implement unique constraint.

THE UNIQUE ANNOTATION

It is very simple and direct. You can see details about implementing your custom annotations in Hibernate Validator docs.

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=UniqueConstraintValidator.class)
@Documented
public @interface Unique {
    String message() default "{constraints.unique}";
    Class<?> entity();
    String field();
    Class<?>[] groups() default {};   
    Class<? extends Payload>[] payload() default {};
}

You can also see the code at GitHub, where trapo is hosted. If I decide to change something, you can get an updated version there. Once you have the @Unique annotation, you must implement the ConstraintValidator referenced by "validationBy" property in @Constraint annotation above. Here is the code using a bean injected by Spring:

public class UniqueConstraintValidator implements ConstraintValidator<Unique, String> {

    @Autowired private SessionFactory sessionFactory;
   
    private Class<?> entity;
    private String field;
   
    public void initialize(Unique annotation) {
        this.entity = annotation.entity();
        this.field = annotation.field();
    }

    public boolean isValid(String value, ConstraintValidatorContext context) {
        if(StringUtils.isEmpty(value)) {
            return false;
        }
        return query(value).intValue() == 0;
    }

    private Number query(String value) {
        HibernateTemplate template = new HibernateTemplate(sessionFactory);
        DetachedCriteria criteria = forClass(entity)
                                   .add(eq(field, value))
                                   .setProjection(count(field));
        return (Number)template.findByCriteria(criteria).iterator().next();
    }

}

Just like the @Unique annotation, you can find the code in GitHub. Now you just need a Validator bean that can be injected in the classes that need it.

<bean id="validator"
      class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />

Voilá!

THE UGLY PART

Right now, Hibernate Validor don't offer any way to know from where your annotations are coming. If you take some time to think about the ConstraintValidator above, you will notice that I manually configure the entity class and field information that were annotated. I need that information to know what query I must execute in order to validate the uniqueness of the field. Bad to me. Really bad. Now I have to provide this information by myself annotating the field like the code highlighted below:

    @NotEmpty @Unique(entity = Forum.class, field = "name")
    private String name;

I can even hear the DRY concept crying! I can't also find a way to reflect this to my schema. Maybe Hibernate Validator can offer some interfaces that we can use to implement all this stuff.

Filed under: code, hibernate, spring, unique, validation

trapo says...

I want to keep Trapo environment as simple as possible. I want that people just fire a command and all tests can run immediately: no setups, no database creation, no sql to run. Just one single command and trapo should do everything for you. Because of that, I start to isolate the test environment from the production one in order to use a different database instance (or even a different database vendor). To integrate the combo, I just call HSQLDB to the party. To avoid describe exactly what is HSQLDB, here is a quote from its homepage:

HSQLDB (HyperSQL DataBase) is the leading SQL relational database engine written in Java. It has a JDBC driver and supports a rich subset of ANSI-92 SQL (BNF tree format) plus many SQL:2008 enhancements. It offers a small, fast database engine which offers both in-memory and disk-based tables and supports embedded and server modes. Additionally, it includes tools such as a minimal web server, in-memory query and management tools (can be run as applets) and a number of demonstration examples.


FIRST, CONFIGURE MAVEN...

The simplest part: just add hsqldb as a maven dependency:

<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <version>1.8.0.10</version>
    <scope>test</scope>
</dependency>

I decide to scope it as test because I'm not intent to use it in production, but you can use a different scope.

SECOND, ADD TEST RESOURCES...

Then, I just create two files inside src/test/resources directory. You can create files here to replace src/main/resources while running tests. But I want to reduce the amount of work to maintain both test and production enviroment, so, I'm replacing just what need to be replace: the database configuration. The two files are:

src/test/resources/hibernate.tests.properties

hibernate.connection.driver_class = org.hsqldb.jdbcDriver
hibernate.connection.url = jdbc:hsqldb:mem:trapo
hibernate.connection.username = sa
hibernate.connection.password =
hibernate.c3p0.min_size=5
hibernate.c3p0.max_size=20
hibernate.c3p0.timeout=1800
hibernate.c3p0.max_statements=50
hibernate.dialect = org.hibernate.dialect.HSQLDialect

hibernate.show_sql=true
hibernate.format_sql=true
hibernate.default_batch_fetch_size=5
hibernate.generate_statistics=true
hibernate.use_sql_comments=true
hibernate.jdbc.batch_size=10

hibernate.cache.use_second_level_cache=true
hibernate.cache.use_query_cache=true
hibernate.cache.provider_class=net.sf.ehcache.hibernate.EhCacheProvider
hibernate.cache.use_structured_entries=true

hibernate.hbm2ddl.auto=create-drop

Note the url property, it is pointing to an in memory database. Moreover, this files contains cache and some other optional configurations to mimic the production configurations. The other file is the Spring context to configure the SessionFactory for tests:

src/test/resources/applicationContextTest.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/util
           http://www.springframework.org/schema/util/spring-util-2.5.xsd">
  <bean id="testSessionFactory"
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
        p:annotatedClasses-ref="annotatedClasses"
        p:annotatedPackages="com.google.code.trapo.domain"
        p:hibernateProperties-ref="hibernateProperties" />
  <util:list id="annotatedClasses">
    <value>com.google.code.trapo.domain.Forum</value>
    <value>com.google.code.trapo.domain.Topic</value>
  </util:list>
  <util:properties id="hibernateProperties" location="classpath:hibernate.test.properties" />
</beans>

I'm not fully confortable with that because I need to copy and past mapped classes here. But, this is a start, so I prefer to have sub-optimal solution over don't have any solution at all.

THIRD, REFACTOR YOUR TESTS...

Your tests should use SpringJUnit4Runner instead of the default one. So you have to put the following annotation in the test class:

@RunWith(SpringJUnit4ClassRunner.class)
public class MyRepositoryTests {
    ...
}

You also need to say to Spring where it should find the configuration files using @ContextConfiguration annotation:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
    "classpath:/trapo-servlet.xml",
    "classpath:/applicationContextTest.xml"
})
public class MyRepositoryTests {
    ...
}

And, because your tests are using the database, is a good call configure them to be transactional. In order to do that, you need both @Transactional and @TransactionConfiguration annotations:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
    "classpath:/trapo-servlet.xml",
    "classpath:/applicationContextTest.xml"
})
@Transactional
@TransactionConfiguration(transactionManager = "txManager")
public class MyRepositoryTests {
    ...
}

And then inject the testSessionFactory configure in applicationContextTests.xml in your tests:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
    "classpath:/trapo-servlet.xml",
    "classpath:/applicationContextTest.xml"
})
@Transactional
@TransactionConfiguration(transactionManager = "txManager")
public class MyRepositoryTests {

    @Autowired private SessionFactory testSessionFactory;

}

FINALLY, JUST RUN YOUR TESTS...

And now the grand finale, just open a command prompt, go to trapo directory and type:

mvn clean test

If you want to see cobertura report, type:

mvn clean cobertura:cobertura

Hope that helps.

Filed under: hibernate, hsqldb, maven, running, spring, tests

Quân says...

- Modify service layer 

modify the SimpleProductManager and add a reference to a ProductDao
'springapp/service/SimpleProductManager.java'

- remove the productManager configuration and the list of products from the springapp-servlet.xml
- configure the service layer in its own application context file 'applicationContext.xml'.
It will be loaded via a servlet listener that we will define in 'web.xml'

- 'springapp/web/WEB-INF/applicationContext.xml'

- using AOP (Aspect Oriented Programming) in the form of a transaction advice and an AspectJ pointcut to define where the transactions should
be applied

The pointcut applies to any method called on the ProductManager interface. The advice is a transaction advice that applies to methods with a name starting with 'save'.
- using the DBCP connection pool

- add lib spring-aspects & dbcp in dom.xml

- run & enjoy

Click here to download:
springapp.rar (13 KB)

Filed under: java, Spring, web

Quân says...

- Using mySQL 

- Create database startup script
'springapp/db/create_products.sql'

'springapp/db/load_data.sql'

Run scripts and load test data

- Create a Data Access Object (DAO) implementation for JDBC
'springapp/repository/ProductDao.java'

'springapp/repository/JdbcProductDao.java'
+ using Spring's JDBC, don't have to worry about opening and closing
the connection or any statements, won't have to catch any exceptions
+ SimpleJdbcDaoSupport provides SimpleJdbcTemplate

+ getSimpleJdbcTemplate().query: provide the SQL statement and a
class that can handle the mapping between the ResultSet and the
Product class
+ ProductMapper implement ParameterizedRowMapper: mapRow map the
data from each row into a class
+ MapSqlParameterSource allows us to use named parameters instead of
the typical "?"

- Add a private field named 'id' complete with setters and getters to Product.java

- Implement tests for JDBC DAO implementation
Add Spring test framework

JdbcProductDaoTests.java

+ extending AbstractTransactionalDataSourceSpringContextTests --> load application context (test-content.xml)
+ onSetUpInTransaction: clear table & load data test, rolled back once the test finishes
test/resources/test-context.xml

test/resources/jdbc.properties

- Run unit test

Click here to download:
springapp.rar (14 KB)

Filed under: java, jdbc, Spring, web

Quân says...

we add a form that will allow the user to enter a percentage price increase 

'springapp/web/WEB-INF/jsp/priceincrease.jsp'

'springapp/service/PriceIncrease.java'

'springapp/service/PriceIncreaseValidator.java'

add an entry in the 'springapp-servlet.xml' file to define the new
form and controller
 + define objects for commandClass and validator
 + formView and a successView
'springapp/web/WEB-INF/springapp-servlet.xml'

controller for this form
 + onSubmit(..) method: get command object
'springapp/web/PriceIncreaseFormController.java'

adding some messages to the 'messages.properties' resource file
'springapp/web/WEB-INF/classes/messages.properties'

add a link to the price increase page from the 'hello.jsp'

re--deploy & run

Click here to download:
springapp.rar (11 KB)

Filed under: java, Spring, web

Quân says...

Add reference to business logic in the controller

- rename HelloController to InventoryController and the HelloControllerTests to InventoryControllerTests
- modify the InventoryController to hold a reference to the ProductManager class
- add code to have the controller pass some product information to the view.
'/springapp/web/InventoryController.java'

Modify the view to display business data and add support for message bundle
- <c:forEach/>: displays product information
- <fmt:message/>: pulls the text to display from a provided 'message' source
'springapp/web/WEB-INF/jsp/hello.jsp'

Add some test data
add a SimpleProductManager to our configuration file and to pass that into the setter of the InventoryController
put the data in 'springapp-servlet.xml' (remember to rename the reference to HelloController to InventoryController)
'springapp/web/WEB-INF/springapp-servlet.xml'

add the 'messageSource' bean entry
'springapp/web/WEB-INF/classes/messages.properties'

Rebuild & run app

Click here to download:
springapp.rar (9 KB)

Filed under: java, Spring, web

Quân says...

- Business:
The application we will be building from scratch over the course of this tutorial is a very basic inventory management system.
In our inventory management system, we have the concept of a product and a service for handling them. In particular, the business has requested the ability to increase prices across all products.
The validation rules for price increase are:
 The maximum increase is limited to 50%.
 The minimum increase must be greater than 0%.
Find below a class diagram of our inventory management system.
- Create Product class
Let's also make it Serializable, not necessary for our application, but could come in handy later on when we persist and store its state.
'springapp/domain/Product.java'

Create the ProductManager
It contains two methods:
  increasePrice(): increases prices for all products
  getProducts(): retrieving all products
'springapp/service/ProductManager.java'
SimpleProductManager class that implements the ProductManager interface
'springapp/service/SimpleProductManager.java'
- SimpleProductManagerTests.java

Click here to download:
springapp.rar (7 KB)

Filed under: java, Spring, web

Quân says...

- using the JSP Standard Tag Library (JSTL) 

pom.xml
 

- create the header file for inclusion in all the JSPs we create.
'springapp/web/WEB-INF/jsp/include.jsp'

- update 'index.jsp' to use JSTL, redirecting to our front Controller.
'springapp/web/index.jsp'

- Move 'hello.jsp' to the 'WEB-INF/jsp' directory. Using the JSTL tag to output variable retrieved from the model
'springapp/web/WEB-INF/jsp/hello.jsp'

- Update the location of the JSP in our controller.
update our unit test class first

test fail

- Update HelloController

- Test again
- Run http://localhost:8084/springapp/ --> redirect to /hello.htm
- Decouple the view from the controller
map to the view using a logical name, remove prefix and suffix
'springapp/web/WEB-INF/springapp-servlet.xml'

update the view name in the controller test & controller to 'hello'
- test & run again

Click here to download:
springapp.rar (5 KB)

Filed under: java, Spring, web

Quân says...

- netbeans > new maven web app
- run index.jsp to see 'Hello World!' message

- Add spring web fw to project

  add to pom.xml

  add to springapp2/web/WEB-INF/web.xml
 + autoload Dispatcher at startup
 + URL with an '.htm' extension be routed to the 'springapp' servlet (the DispatcherServlet)

- Add new file springapp2/web/WEB-INF/springapp-servlet.xml


- Create the Controller
com.mycompany.springapp2.web.HelloController
- Test Controller
HelloControllerTests.java

- Create the View
'springapp2/web/hello.jsp':
- Compile, deploy & run the application: http://localhost:8084/springapp2/hello.htm

Click here to download:
springapp2.rar (4 KB)

Filed under: java, Maven, Spring

Matt says...

Presenter: Joseph Nusairat, Integrallis About This Talk

  • discuss web flows
  • what are spring web flows
  • show some basics of webflow
  • live demo
  • Grails web flows didn't really mature until Grails 1.1.1

What is Web Flow

  • allows for controlling the flow of items via steps, substeps, other flows
  • use when you want to control the sequence in which events occur in a series of events
    • e.g. buying a plane ticket on travelocity--goes through a specific series of steps
    • can't go to point D without going through A, B, and C first
    • also support branches
  • control is not on the page but in a DSL
  • can keep components open for a longer period of time
  • where do you store the information for something like an uncompleted plane ticket order?
    • can store in the database, but would require cleanup for things that never make it to checkout
    • general way people have done this is via session data
      • check out spring memory management session recording on infoq
      • don't want to use up a lot of your server RAM with sessions that are unused
  • at the end of the flow, data is persisted to a database or somewhere more permanent
  • when you remove the flow aspect of things from the pages themselves, it makes things more flexible and configurable
  • SEAM allows for different timeouts for conversations and sessions themselves

WebFlows in Spring

  • was new in Spring 2.0
  • great integration with JSF for rich UI
  • allows control over the scoping of objects for a limited period of time
  • cannot have multiple conversations
  • uses xml
    • xml is great for transferring objects, but not great for configuration
    • configurations aren't generally that black and white

Creating a WebFlow

  • WebFlow in 1.2 M3 doesn't work at the moment
  • GRAILS-5185
  • demo will be in 1.1.1
  • in 1.2 WebFlow is a plugin (grails install-plugin webflow)
    • in 1.1.1 it's built in, but in 1.2 you have to install the plugin to use it
  • flows live in the controller
    • look just like an action, except ends with "Flow"
      • e.g. def bookingCartFlow = {}

Defining a View

  • views represent the individual pages
  • demo of hotel booking flow
    • "do you want to pay via paypal?" -- decision point with branch
  • def bookingCartFlow = {
    start() {}
    }
    • whatever is defined first is the starting point--doesn't have to be called start
  • DSL used to direct the flow, e.g. on("submit").to "end"

Domain Model

  • don't have to worry too much about the domain model except for the object that's used to track the conversation
  • Booking class is what's used to track the conversation and isn't persisted until the end
    • must implement Serializable in order to work

Creating a Controller and Actions

  • create a controller as per usual, but create an action ending with "Flow"
  • def bookingCartFlow = {
    start() {
    on("next") {
    // do work here
    log.info "inside start"
    }.to "stepA"
    on("done").to "end"
    }

    stepA() {
    on("prev").to "start"
    on("done").to "end"
    }

    end() {
    // define action state
    action {
    log.info "finish what you're done with"
    }
    }
    }

  • make sure to follow the DSL--doesn't necessarily error out if you aren't following the DSL
  • examples here are all done in the controller but in a real app you'd be using a service layer
    • according to the docs transactional needs to be set to false, but it doesn't work this way currently (1.1.1)
    • can do with transaction inside services if you're trying to save states in a particular order

Views

  • in flows you can have subfolders below the view directory for the controller
  • class HotelController {
    def index = { redirect(action:'bookingCart') } // this will go to the bookingCartFlow

    def paypalFlow = {
    pay() {
    on("submit") {
    log.info "Was it paid? ${params.paid}"
    }.to "evaluatePayment"
    }

    evaluatePayment() {
    action {
    if (params.paid == 'yes') {
    conversation.booking.paid = true
    return paidInFull
    } else {
    return paymentDeclined()
    }
    }
    }
    }

    def bookingCartFlow = {
    start() {
    action {
    log.info = "-start the booking-"
    }
    on("success").to "findHotels"
    }

    findHotels() {
    on("showHotels") {
    // find the hotel
    def hotels = Hotel.findAllByNameILike("%${params.hotelName}%", [max:10])
    [hotelResults : hotels]
    }.to "hotelListPage"
    }

    hotelListPage() {
    on("view") {
    flow.hotel = Hotel.get(params.id)
    }.to "hotelView"

    on("back") {
    }.to "findHotels"
    }

    hotelView() {
    on("back").to "start"
    on("book").to "bookingPage"
    }

    bookingPage() {
    on("proceed") {
    def booking = new Booking(params)
    booking.hotel = flow.hotel
    conversation.booking = booking
    if (!booking.validate()) {
    return error() // returns back to the page they came from and outputs errors
    }
    }.to "saveBooking"
    }

    saveBooking() { // the parens here are optional
    // no corresponding view to "saveBooking" so this is a decision node
    action {
    if (params.paymentType == "creditCard") {
    return creditCard()
    } else {
    return paypal()
    }
    }
    on("creditCard").to "creditCard"
    on("paypal").to "paypal"
    }

    paypal() {
    subflow(paypalFlow)
    on("paidInFull")
    }

    creditCard() {}
    }
    }

  • to identify an event in a flow, you can use the name of a g:button as the event you want to trigger when the button is clicked
  • to use an event in a g:link, add event parameter to g:link, e.g. <g:link event="view" ... />

Scopes Available in Flows

  • request
  • params
  • session
  • flash
  • flow
    • lasts for the duration of the flow
    • removed when flow ends
  • conversation
    • lasts for the length of the flow and nested subflows
    • subflows are good for things like different payment methods--can be reused across multiple flows
  • best practice point: don't refer to anything about flows or conversations in gsps
    • this ties the view to a specific flow or conversation and reduces the ability to reconfigure and reuse things

What are subflows

  • ability to create alternative flow paths
  • call subflows via flows
  • are their own flows themselves
  • name with "Flow" at the end
  • calling a subflow
    • subflow(subflowName)

Can you call to flows in different controllers?

  • yes, but it gets a bit funky
  • define flow in another controller as per usual, but declare the flow as static
  • in other controller, do def creditcardFlow = BillingController.creditcardFlow
  • still have to put the billing views under the hotel views as opposed to the billing views

How do you end the flow?

  • if a flow doesn't do a transition or a redirect, the flow ends

Final thoughts

  • don't call services with transactions--can cause unexpected problems
  • in grails 1.2 flows are a plugin
  • webflow moves objects from flash to request scope between transition states
  • don't include the scope prefix when calling on the page
    • merge flow/conversation to local scopes

http://github.com/integrallis/grails_web_flow_demo

Filed under: Conferences, Grails, Groovy, Spring, SpringOne2GX, WebFlow