How to Add Your Own DAO to the GSF Actions

image

GSF's ActionController and Action classes (in GSF version 11.x) can make building a website in WebCenter SItes much easier. If you're not familiar with the GSF and why you should consider using it, then check out Tony's blog post. If you're building a website in WebCenter Sites, then there's a good chance you're not making a small "Hello World" website. Big, complex websites require lots of assets, which in turn can require lots of code to load and manage all these assets. And plus there's usually custom form-processing and third-party web service integrations.

Well don't fret, because with a few simple steps, you can customize your GSF ActionController to add your own custom DAOs or web service integrations. You can create your own custom DAO -- for loading your assets with your own custom logic -- and then reuse it all across your site with very little effort. Similarly, this also makes it easy to add web service integrations that you can use in your Acion classes.

CUSTOMIZING YOUR ACTION CONTROLLER

First, make sure you're using the right GSF controller. Make sure your GST/Dispatcher element calls the ActionController class like has this:

<CALLJAVA CLASS="com.fatwire.gst.foundation.controller.action.ActionController" />

Next, extend a few GSF classes. First, create a class that extends the com.fatwire.gst.foundation.controller.action.support.DefaultWebAppContext class and override the "getFactory" method like this:

public class MyWebAppContext extends DefaultWebAppContext {

    public MyWebAppContext(final ServletContext context) {
        super(context);
    }

    public MyWebAppContext(final ServletContext context, final AppContext parent) {
        super(context, parent);
    }

    @Override
    public Factory getFactory(final ICS ics) {
        // called very often; once per request/pagelet, scoped per ICS context
        return new MyObjectFactory(ics);
    }
}

You'll notice the "getFactory" method returns a Factory object of class MyObjectFactory, which is the next class you need to create. So create a class called MyObjectFactory that extends class com.fatwire.gst.foundation.controller.action.support.IcsBackedObjectFactoryTemplate. IcsBackedObjectFactoryTemplate already contains factory method for creating many of the GSF DAO and service classes (like NavigationService or TemplateAssetAcces). In the MyObjectFactory class, we can customize the GSF Action Controller's behavior. We can override any IcsBackedObjectFactoryTemplate methods (perhaps you have your own NavigationService you'd like to use) or we can add our own methods for creating our own DAOs (or web services). For example, if you have your own asset DAO named MyAssetDao (for doing simple asset loading tasks), then you'd create the class like this:

public class MyObjectFactory extends IcsBackedObjectFactoryTemplate {

    public MyObjectFactory(ICS ics) {
        super(ics);
    }

    public MyObjectFactory(ICS ics, Factory... roots) {
        super(ics, roots);
    }

    @ServiceProducer(cache=true)
    public MyAssetDao createMyAssetDao(ICS ics) {
        return new MyAssetDao(ics);
    }
}

Next, register your MyWebAppContext class on your application server by adding these lines to your /cs/WEB-INF/web.xml:

<context-param>
    <param-name>gsf-contexts</param-name>
    <param-value>com.mywebsite.owcs.config.MyWebAppContext</param-value>
</context-param>

Now you're ready to start creating and using Action classes. For example, maybe you have an "Article" Page Definition that you need to load. So, create a class named ArticlePage which can be reused in multiple templates and also uses MyAssetDao.

package com.mywebsite.owcs.action;

import COM.FutureTense.Interfaces.ICS;
import com.fatwire.assetapi.data.AssetId;
import com.fatwire.gst.foundation.controller.action.Action;
import com.fatwire.gst.foundation.controller.action.Model;
import com.fatwire.gst.foundation.controller.annotation.InjectForRequest;
import com.fatwire.gst.foundation.facade.assetapi.asset.TemplateAssetAccess;

public class ArticlePage implements Action {
    /**
     * Inject an ICS into the action for convenience
     */
    @InjectForRequest
    public ICS ics;

    /**
     * Provide a place to put the data that will be passed into the page context for use with expression language
     */
    @InjectForRequest
    public Model model;

    /**
     * Provide a DAO that allows an asset to be easily mapped
     */
    @InjectForRequest
    protected TemplateAssetAccess templateAssetAccess;

    @InjectForRequest
    protected MyAssetDao myAssetDao;

    public void handleRequest(ICS ics) {
        // get the asset id corresponding to the ICS variables c, cid
        AssetId id = templateAssetAccess.currentId();

        // load ArticleDetail page and add it to the page context
        model.add("asset", myAssetDao.loadArticlePage(id));
    }
}

Notice we only needed two lines of code to include our DAO

@InjectForRequest
protected MyAssetDao myAssetDao;

And then one function call to load the Article Page. Now we can use this ArticlePage action multiple templates, simply by including this JSP call at the beginning of the template:

<cs:ftcs><gsf:root action="class:com.mywebsite.owcs.action.ArticlePage">

And if you want to add your own services so they can be injected into your action classes, just add the "create" method to your MyObjectFactory like this:

@ServiceProducer(cache=true)
public MySolrService createMySolrService(ICS ics) {
    return new MySolrService(ics);
}

And then inject them into your Action class with these two lines:

@InjectForRequest
protected MySolrService mySolrService;

And that's all there is to it!

TROUBLESHOOTING

Be careful how you name the objects (DAOs) that you inject into your Action classes as well as the "create" methods in your factory class. I ran into an issue where the GSF controller was trying to inject the wrong object class for my DAO, which creates a ClassCastException. And after looking at exception in the error logs, it wasn't immediately clear what the cause was or how to fix it. The error message I saw looked like this:

java.lang.ClassCastException: com.fatwire.gst.foundation.facade.assetapi.AssetAccessTemplate cannot be cast to com.fatwire.gst.foundation.facade.assetapi.asset.TemplateAssetAccess

To fix it, we had to do a combination of things:

1) Enable DEBUG for logger com.fatwire.gst.foundation.controller.action. This greatly helped to narrow down the cause of the issue.
2) Rename the object instances that you're injecting into your Action classes. I'm talking about this:

@InjectForRequest
protected MyAssetDao myAssetDao;

3) Create addtional "create" methods in your MyObjectFactory class and make use of the @ServiceProducer "name" annotation. The name of your "create" method matters when it comes time to do the annotation injection. For example, in my factory class, I added:

@ServiceProducer(cache = true, name="assetDao")
public TemplateAssetAccess createAssetDao(ICS ics) {
    return new TemplateAssetAccess(ics);
}

And then in my Action class, I can inject it like this:

@InjectForRequest
protected TemplateAssetAccess assetDao;

Using these three strategies (especially the debug Logger I just mentioned) should help you track down any ClassCastException injection issues you encounter.

Good luck and let me know if you have any questions in the comments below.

Subscribe to Our Newsletter

Stay In Touch