PDA

View Full Version : Accessing Service Objects in Actions



curtney
Jun 10th, 2005, 01:57 PM
I have an action class that needs to access Product and Catalog information in my database for display. What is the established practice for accessing service objects within spring webflow actions?

So far, I have come accross one listing that said to have actions be context aware.

public class ActionObject extends AbstractAction implements ApplicationContextAware

Any other alternatives?

_CJ

Keith Donald
Jun 10th, 2005, 04:46 PM
Look how the Phonebook sample does this.

Do not use ApplicationContextAware to do a service lookup. Have Spring inject the reference to the service object to the action through a setter or constructor argument.

Keith

curtney
Jun 10th, 2005, 06:15 PM
That is what I have done and for some strange reason whenever I call my event method in my Action class I am getting a NullPointerException. I know my wiring is fine, because I am calling this same service object in one of my spring mvc controllers (which uses setter injection) and everything works fine. Anyways, I thought perhaps it had to do with spring webflow.

method interface:

public Event getCategory (RequestContext context) throws Exception


In the above method I check to see if the reference to the service object is null, and it is. However, I know in my Action class that service object reference is set when the application loads. I did a simple println inside the setter method to see if it is getting called.

Below is the exception I am getting.

java.lang.NullPointerException
at com.oim.commerce.web.spring.flow.action.CatalogAct ion.getCategory(CatalogAction.java:65)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Nativ e Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Native MethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(De legatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
at org.springframework.util.DispatchMethodInvoker.dis patch(DispatchMethodInvoker.java:203)
at org.springframework.web.flow.action.MultiAction.do Execute(MultiAction.java:137)
at org.springframework.web.flow.action.AbstractAction .execute(AbstractAction.java:141)
at org.springframework.web.flow.ActionState$ActionExe cutor.execute(ActionState.java:439)
at org.springframework.web.flow.ActionState.doEnter(A ctionState.java:363)
at org.springframework.web.flow.State.enter(State.jav a:164)
at org.springframework.web.flow.execution.impl.StateC ontextImpl.spawn(StateContextImpl.java:179)
at org.springframework.web.flow.execution.impl.FlowEx ecutionImpl.start(FlowExecutionImpl.java:279)
at org.springframework.web.flow.execution.FlowExecuti onManager.onEvent(FlowExecutionManager.java:267)
at org.springframework.web.flow.execution.FlowExecuti onManager.onEvent(FlowExecutionManager.java:245)
at org.springframework.web.flow.execution.servlet.Ser vletFlowExecutionManager.handle(ServletFlowExecuti onManager.java:77)
at org.springframework.web.flow.mvc.FlowController.ha ndleRequestInternal(FlowController.java:137)
at org.springframework.web.servlet.mvc.AbstractContro ller.handleRequest(AbstractController.java:128)
at org.springframework.web.servlet.mvc.SimpleControll erHandlerAdapter.handle(SimpleControllerHandlerAda pter.java:44)
at org.springframework.web.servlet.DispatcherServlet. doDispatch(DispatcherServlet.java:684)
at org.springframework.web.servlet.DispatcherServlet. doService(DispatcherServlet.java:625)
at org.springframework.web.servlet.FrameworkServlet.s erviceWrapper(FrameworkServlet.java:386)
at org.springframework.web.servlet.FrameworkServlet.d oGet(FrameworkServlet.java:346)
at javax.servlet.http.HttpServlet.service(HttpServlet .java:113)
at javax.servlet.http.HttpServlet.service(HttpServlet .java:90)
at com.caucho.server.dispatch.ServletFilterChain.doFi lter(ServletFilterChain.java:99)
at org.springframework.orm.hibernate3.support.OpenSes sionInViewFilter.doFilterInternal(OpenSessionInVie wFilter.java:172)
at org.springframework.web.filter.OncePerRequestFilte r.doFilter(OncePerRequestFilter.java:76)
at com.caucho.server.dispatch.FilterFilterChain.doFil ter(FilterFilterChain.java:70)
at com.caucho.server.webapp.WebAppFilterChain.doFilte r(WebAppFilterChain.java:163)
at com.caucho.server.dispatch.ServletInvocation.servi ce(ServletInvocation.java:207)
at com.caucho.server.http.HttpRequest.handleRequest(H ttpRequest.java:249)
at com.caucho.server.port.TcpConnection.run(TcpConnec tion.java:327)
at com.caucho.util.ThreadPool.runTasks(ThreadPool.jav a:450)
at com.caucho.util.ThreadPool.run(ThreadPool.java:394 )
at java.lang.Thread.run(Thread.java:534)

Keith Donald
Jun 10th, 2005, 07:43 PM
Is Spring instantiating your action? Are you referring to it by id from your webflow definition? Is your setter method correctly coded? Let me see your action bean definition, your action setter logic, how the service is used in the getCategory method, and your corresponding action state def.

Keith

curtney
Jun 10th, 2005, 08:41 PM
Yeah, I pretty much checked everything that I can think off, including what you previously listed.

The following is what I have:

public class CatalogAction extends MultiAction {
private static final String SHOW_CATEGORY = "showCategory";
private CatalogService catalogService;
private CategoryService categoryService;

public CatalogAction () {
super();
}
public void setCatalogService (CatalogService catalogService) {
this.catalogService = catalogService;
}
public void setCategoryService (CategoryService categoryService) {
this.categoryService = categoryService;
}

// 1. If the eventId is showCategory

// a. Retrieve the category id from request

// b. Retrieve the category and its' subcategories

// c. Test whether this category has any products.

// 1. if so store within request context the category's subcategories and products and set eventId to showCategoriesAndProducts

// 2. else store the category's subcategories within the request context and set eventId to showOnlyCategories

public Event getCategory (RequestContext context) throws Exception {
if (context.getSourceEvent().getId().equals(SHOW_CATE GORY)) {
Long id = Long.valueOf(context.getSourceEvent().getParameter ("id").toString());
if (id != null) {
Category category = categoryService.getCategory(id);
}
}
return result("showOnlyCategories");
}

/************** Flow Definition ***************************/
public class BrowseCatalogFlowBuilder extends AbstractFlowBuilder {

//flow id
private static final String FLOW_ID = "catalog.Browse";
// View ids
private static final String VIEW_ONLY_CATEGORIES_ID = "viewOnlyCategoriesId";
private static final String VIEW_CATEGORIES_AND_PRODUCTS_ID = "viewCategoriesAndProductsId";
private static final String VIEW_PRODUCT_DETAIL_ID = "viewProductDetailId";
//Action ids
private static final String ACTION_SELECT_CATEGORY_ID = "actionSelectCategoryId";
private static final String ACTION_GET_PRODUCT_ID = "getProductId";
//View names
private static final String VIEW_ONLY_CATEGORIES = "ViewOnlyCategories";
private static final String VIEW_CATEGORIES_AND_PRODUCTS = "ViewCategoriesAndProducts";
private static final String VIEW_PRODUCT_DETAIL = "ViewProductDetail";
//Action method names
private static final String ACTION_GET_PRODUCT_METHOD = "getProduct";

protected String flowId() {
return FLOW_ID;
}
public void buildStates() throws FlowBuilderException {

addActionState (ACTION_SELECT_CATEGORY_ID, method ("getCategory", action (CatalogAction.class)),
new Transition[] { on ("showOnlyCategories", VIEW_ONLY_CATEGORIES_ID),
on ("showCategoriesAndProducts", VIEW_CATEGORIES_AND_PRODUCTS_ID)});

addViewState (VIEW_ONLY_CATEGORIES_ID, VIEW_ONLY_CATEGORIES,
new Transition[] {on("showCategory", ACTION_SELECT_CATEGORY_ID)});

addViewState (VIEW_CATEGORIES_AND_PRODUCTS_ID, VIEW_CATEGORIES_AND_PRODUCTS,
new Transition[] {on("showCategory", ACTION_SELECT_CATEGORY_ID),
on ("showProduct", ACTION_GET_PRODUCT_ID)});

addActionState (ACTION_GET_PRODUCT_ID, method (ACTION_GET_PRODUCT_METHOD, action (ProductAction.class)), on("showProductDetail",VIEW_PRODUCT_DETAIL_ID));

addEndState (VIEW_PRODUCT_DETAIL_ID, VIEW_PRODUCT_DETAIL);

}

/********* Relevant webflow-context *******************/
<bean id="catalogBrowseFrontController" class="org.springframework.web.flow.mvc.FlowController">
<property name="flow" ref="catalog.Browse"/>
</bean>

<bean id="catalog.Browse" class="org.springframework.web.flow.config.FlowFactoryBea n">
<property name="flowBuilder">
<bean class="com.oim.commerce.web.spring.flow.BrowseCatalogFlow Builder" autowire="byType"/>
</property>
</bean>

<bean id="catalog.Browse.catalogMultiAction" class="com.oim.commerce.web.spring.flow.action.CatalogAct ion">
<property name="categoryService">
<ref bean="categoryService"/>
</property>
<property name="catalogService">
<ref bean="catalogService"/>
</property>
</bean>

Keith Donald
Jun 10th, 2005, 08:49 PM
Ok the problem is in your action state def and your use of the action factory method. The method you're using has the flow system --- not the spring bean factory -- instantiate a new action instance on each call. When you do this, the default case performs no service autowiring, and that's why your setters for your services aren't being called.

Since you already have your services and actions managed by spring, the best thing to simply replace "action" with "actionRef"... see the javadocs for these two methods to compare what they do...

e.g actionRef(CategoryAction.class)

That does a by-type lookup for an existing action in the spring bean factory instead of instantiating a new one...

Keith

curtney
Jun 10th, 2005, 10:30 PM
Wow, I spent the whole day debugging this, and it was just a matter of switching "action" to "actionRef"

Thanks Keith.

functionform
Dec 28th, 2006, 04:37 PM
Look how the Phonebook sample does this.

Do not use ApplicationContextAware to do a service lookup. Have Spring inject the reference to the service object to the action through a setter or constructor argument.

Keith


Thanks Keith this is a good answer. I've been looking for the place where the line between a service and an action is drawn and/or the tension between the two. Maybe I missed this in the documentation, but maybe this should be under a best-practices/quick-start type area. Still I guess maybe the key question is "will this Action functionality be re-used independently from the Action, and if so, it should probably be placed into a service.". Otherwise it's really tempting to just place all logic into the Action itself... Hm.

Also I don't believe phonebook is currently doing this; looking at the search-flow.xml file it looks like it's using a bean action within a view-state. Of course the original post was over a year ago, hehe.

Of course I'm a newbie at both Spring and Webflow so I could be insane/reading it incorrectly ;)

klr8
Dec 29th, 2006, 02:55 AM
You're pretty much correct in your analysis. And indeed, in SWF 1.0 the phonebook no longer has any real action code in it.

Erwin