Showing posts with label springframework. Show all posts
Showing posts with label springframework. Show all posts

Tuesday, February 9, 2010

Customization of webflows

With the new 1.7 release of sculptor the possibilities to customize flows for the web client is much better.
With our Library example here is what you can do.
For example, lets say you want to implement filter functionality for the library list feature. Here is the steps to do that:

1) Specify that you want gap-files for the list library feature, so in your model.guidesign:

gui Library for Library {
Module for media {
ListTask for Library {
gap
}
}
}

Now you have a bunch of files for the feature:
  • src/generated/java/org...library/ListLibraryActionBase.java
  • src/main/java/org...library/ListLibraryAction.java -> gap
  • src/generated/java/org...library/ListLibraryForm.java
  • src/WEB-INF/generated/flows/media/listLibrary/listLibrary-base.xml
  • src/WEB-INF/generated/flows/media/listLibrary/list_include.xhtml
  • src/WEB-INF/flows/media/listLibrary/listLibrary-flow.xml -> gap
  • src/WEB-INF/flows/media/listLibrary/list.xhtml -> gap

2) Edit the media/listLibrary flow:


<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:ns0="http://www.w3.org/2001/XMLSchema-instance"
ns0:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd"
parent="media/listLibraryBase">
<view-state id="list">
<transition on="filterLibrary" to="listByFilter" />
</view-state>
<view-state id="listByFilter" model="listLibraryForm"
view="/WEB-INF/flows/media/listLibrary/list.xhtml" parent="media/listLibraryBase#list">
<on-render>
<evaluate
expression="listLibraryAction.findByFilter(flowRequestContext)" />
</on-render>
</view-state>
</flow>

3) Edit the ListLibraryAction, add the method:


public String findByFilter(RequestContext ctx) {
getRepository().clear();

List<library> allLibraries = getLibraryService().findAll(ServiceContextStore.get());
List<library> filtered = new ArrayList<library>();
String filter = ctx.getRequestParameters().get("libraryFilter");
for (Library library : allLibraries) {
if (library.getName().startsWith(filter)) {
filtered.add(library);
}
}
formObject(ctx).setAllLibraries(filtered);
return "success";
}

4) Add a simple form with a text field and a button to the media/listLibrary/list.xhtml-file:


<html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core" xmlns:t="http://myfaces.apache.org/tomahawk"
xmlns:h="http://java.sun.com/jsf/html" xmlns:c="http://java.sun.com/jstl/core"
xmlns:a="ApplicationTaglib">
<body>
<ui:composition template="/WEB-INF/common/template.xhtml">
<ui:define name="content">
<h1>
<h:outputFormat value="#{msg['list.header']}">
<f:param
value="#{msgMedia['model.DomainObject.Library.plural']}" />
</h:outputFormat>
</h1>
<h:form xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core"
xmlns:t="http://myfaces.apache.org/tomahawk"
xmlns:h="http://java.sun.com/jsf/html" xmlns:c="http://java.sun.com/jstl/core"
xmlns:a="ApplicationTaglib">
<div>
<label for="_libraryFilter">#{msgMedia['model.DomainObject.Library.filter']}: </label>
<input type="text" value="#{requestParameters.libraryFilter}" name="libraryFilter" id="_libraryFilter"/>
<h:commandButton value="#{msgMedia['model.DomainObject.Library.filterButton']}" action="filterLibrary" />
</div>
</h:form>
<ui:include
src="/WEB-INF/generated/flows/media/listLibrary/list_include.html" />
</ui:define>
</ui:composition>
</body>
</html>

Note that the default generated table showing the list result is used by including the generated file. In case you need to modify the generated content you can either overwrite the code generation templates in WebSpecialCases.xpt or simply copy the generated file and maintain it manually

That's it.

Saturday, October 24, 2009

Decouple modules with asynchronous event dispatching using Spring and task queues in GAE

To build applications that are maintainable and robust you should strive for decoupling between modules. To build applications that scale you will always benefit from asynchronism and parallellism.
Here we will look how to accomplish the above in GoogleAppEngine and with some help from springframework.
Lets say we have an application where users can register them self. When they do, the application creates a persistence instance of a User-object. But, we will also keep track of how many users we have registered on the site. Now, being in GAE with BigTable luring in the back, doing queries and calculations (as we are used to with a traditional database) isn't a good idea. So as an alternative we choose to have a separate Counter-object that we updates when ever a new user registers. Ok, nothing strange here. But, there are a couple of flaws here:
  1. The User module needs to know about the Counter module.
  2. The User module has to wait for the Counter module to finish when updating the counting.
Ok, lets solve the first by using spring's mechanism for ApplicationEvent's. First, let us put some aop magic to work to intercept the call to UserService.createUser and when it returns (and we have the transaction boundaries on service methods, so no exception, all went well) fire off an event. Spring config for the aop stuff:


<bean id="userListener" class="org.fornax.sculptor.UserListener"/>
<bean id="userAdvice" class="org.fornax.sculptor.UserAdvice"/>
<aop:config>
<aop:pointcut id="userCreationPointcut" expression="execution(public * org..UserService.createUser(..))"/>
<aop:advisor pointcut-ref="userCreationPointcut" ref="userCreationPointcut"/>
</aop:config>


Next, here is the advice:

public class UserAdvice implements MethodInterceptor, ApplicationContextAware {

private ApplicationContext ctx;

public Object invoke(MethodInvocation invocation) throws Throwable {
User user = (User) invocation.proceed();
fireNewUserEvent(user);
return user;
}

private void fireNewUserEvent(User user) {
ctx.publishEvent(new UserCreatedEvent(user));
}

public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.ctx = applicationContext;
}
}

The listener that is being notified:

public class UserListener implements ApplicationListener<UserCreatedEvent> {
@Autowired private CounterService counterService;
public void onApplicationEvent(UserCreatedEvent event) {
counterService.increment();
}
}

And the event being passed:

public class UserCreatedEvent extends ApplicationEvent {
public UserCreatedEvent(User user) {
super(user);
}
}

Ok, so now we are half way. We have the Observer pattern in place. But we still does everything synchronous.
Enter GAE's task queue's. Let us modify our UserListener:

public class UserListener implements ApplicationListener<UserCreatedEvent> {

public void onApplicationEvent(UserCreatedEvent event) {
TaskOptions task = url("/rest/admin/counter/user").method(POST);
Queue queue = QueueFactory.getDefaultQueue();
queue.add(task);
}
}

And by the wonders of task queue's, we now put a task on the queue and by that we do the counting job asynchronous. And of course, we dropped the reference to the CounterService. But we miss one piece here, right? What does the url in the task point at. Well, nothing strange here, it is just a spring mvc controller:

@Controller
public class CounterCountroller {
@Autowired private CounterService counterService;
@RequestMapping(value = "/admin/counter/user", method = RequestMethod.POST)
public void incrementCounter() throws IOException {
try {
counterService.increment();
} catch (Exception ignore) {
// doesn't matter if we get an exception here, just log it
log.error("Failed to increment counter!", ignore);
}
}
}
And now we have a more loosely coupled system that scales better. And with a little effort, the code can be generalized so more features are easy to add with the same pattern.
Of course, the downside of this kind of design is that error handling gets more complicated and you can't always trust it to be 'right'. But that is system design, you have to decide what's best for each situation.