Rewrote the blog in Ruby on Rails 3

Posted by wsargent Thu, 25 Oct 2007 06:21:00 GMT

Rewrote the blog in Ruby on Rails. Thanks, Aaron. Comments are now back online, after I had to shut them off due to comment spam. The blog now runs all comments through Akismet.

I don’t really have much to say about Ruby or Rails that hasn’t been said by many people before; I just have my experience of it, which I think is more valuable than the technical minutiae.

I think the biggest strength of Rails (and Ruby for that matter) isn’t the framework. It’s the community.

The framework solves the problem it’s supposed to solve. The community is all aligned around an agile mindset. As such, there’s not the “There’s more than one way to do it” philosophy of Perl. Instead, the philosophy is “How do we make this simple, flexible and easy to build on?” When you’re working along the same assumptions, Rails makes it very, very easy to do what you want. The community supports that, not just with tutorials, but with podcasts, screencasts, and books.

However, if you’re doing something that isn’t in that set of assumptions – if you have legacy data or non-Railsy database schema and associations – you’re off the map. In a way, this was good because I got to figure out how Rails really works, but it was certainly a dunk in cold water after having so much help available.

This has been something I’ve wanted to do for years. Gave up some features for it; no automatic java syntax highlighting, no full text search, and no filtering by categories. But I’ve got the bones, and I can work on the flesh later. Now, sleep.

BeanshellAdminService

Posted by wsargent Sun, 12 Feb 2006 03:46:00 GMT

I wrote my blog from the ground up. It seemed like an interesting technical exercise at the time. Now I have actual content and people who comment, I’m in a much better position to appreciate the benefits and drawbacks.

Plus: I know exactly what’s going on. It does exactly what I want.
Minus: If something doesn’t work, I can’t draw on a user community to fix it for me.
Plus: I can use the blog as a proving ground for new technology.
Minus: Spammers also learn new technology.

I thought I was very clever for writing my own blog, because spammers were oriented against WordPress and Blogger and wouldn’t bother with as small a target as one custom written blog. I failed to take into account that spammer technology would advance to the point where they would figure out how to act like users and hit a comment button without regard to the back end technology.

I tried cleaning out the spam manually. This is like trying to stop the tide with a bucket. The problem is to come up with a comment submission scheme that spammers cannot hack, and that I need to think about.

In the meantime, I’ve disabled comments altogether. And in lieu of deleting the comments by hand, I’ve used this problem as an opportunity to think about how to do ad-hoc deletes from the repository.

Hence the BeanshellAdminService. This little beauty runs in the AdminServer, and allows me to run beanshell scripts on the server. It’s so convenient that I can’t believe I didn’t think of it years ago. For development, it lets you do just about anything.

admin

I’ve uploaded it to the usual place. This is useful for your normal services as well. You can override the admin servlet to set your own parameters for the interpreter, so if there’s a function that you want to pull in or some parameters that you want to set, you can set them with an anonymous subclass. Best to do that from another module though, so you don’t inadvertently deploy scriptable code to production…

Environment configuration with Spring 2

Posted by wsargent Fri, 20 Jan 2006 07:46:00 GMT

Hokay. I have a workable configuration environment now.

There’s one master Spring file, and two subsiduary files that contain environment specific properties.

The main file is below (minus the data source):

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
  <property name="exposeTransactionAwareSessionFactory" value="false"/>
  <property name="dataSource" ref="dataSource"/>
  <property name="mappingResources">

&lt;list&gt;
  &lt;value&gt;com/tersesystems/blog/dao/hibernate/PostImpl.hbm.xml&lt;/value&gt;
  &lt;value&gt;com/tersesystems/blog/dao/hibernate/CommentImpl.hbm.xml&lt;/value&gt;
  &lt;value&gt;com/tersesystems/blog/dao/hibernate/CategoryImpl.hbm.xml&lt;/value&gt;
  &lt;value&gt;com/tersesystems/blog/dao/hibernate/MemberImpl.hbm.xml&lt;/value&gt;
  &lt;value&gt;com/tersesystems/blog/dao/hibernate/MediaImpl.hbm.xml&lt;/value&gt;
  &lt;value&gt;org/appfuse/model/Role.hbm.xml&lt;/value&gt;
  &lt;value&gt;org/appfuse/model/User.hbm.xml&lt;/value&gt;
  &lt;value&gt;org/appfuse/model/UserCookie.hbm.xml&lt;/value&gt;
&lt;/list&gt;

</property>
<property name=”hibernateProperties” ref=”hibernatePropertiesBean”/>
</bean>

Note the references to the hibernatePropertiesBean. This is an instance of PropertiesFactoryBean that will be defined in the environment Spring file.

The JTA Spring file is defined below:

<bean id="hibernatePropertiesBean" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
  <property name="properties">

&lt;props&gt;
  &lt;prop key="hibernate.dialect"&gt;${hibernate.dialect}&lt;/prop&gt;
  &lt;prop key="hibernate.cache.use_query_cache"&gt;${hibernate.cache.use_query_cache}&lt;/prop&gt;         
  &lt;prop key="hibernate.cache.use_second_level_cache"&gt;${hibernate.cache.use_second_level_cache}&lt;/prop&gt;        
  &lt;prop key="hibernate.cache.provider_class"&gt;${hibernate.cache.provider_class}&lt;/prop&gt; 
  &lt;prop key="hibernate.current_session_context_class"&gt;${hibernate.current_session_context_class}&lt;/prop&gt;
  &lt;prop key="hibernate.show_sql"&gt;${hibernate.show_sql}&lt;/prop&gt;        
  &lt;prop key="hibernate.connection.release_mode"&gt;auto&lt;/prop&gt;
  &lt;prop key="hibernate.query.substitutions"&gt;${hibernate.query.substitutions}&lt;/prop&gt;          
  &lt;!-- Must be enabled for JTA transactions... --&gt;    
  &lt;prop key="hibernate.transaction.manager_lookup_class"&gt;${hibernate.transaction.manager.lookup}&lt;/prop&gt;  
&lt;/props&gt;

</property>
</bean>

and the JDBC one is defined likewise. Note that we can’t have even an empty property existing for hibernate.transaction.manager.lookup here.

<bean id="hibernatePropertiesBean" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
  <property name="properties">

&lt;props&gt;
  &lt;prop key="hibernate.dialect"&gt;${hibernate.dialect}&lt;/prop&gt;
  &lt;prop key="hibernate.cache.use_query_cache"&gt;${hibernate.cache.use_query_cache}&lt;/prop&gt;         
  &lt;prop key="hibernate.cache.use_second_level_cache"&gt;${hibernate.cache.use_second_level_cache}&lt;/prop&gt;        
  &lt;prop key="hibernate.cache.provider_class"&gt;${hibernate.cache.provider_class}&lt;/prop&gt; 
  &lt;prop key="hibernate.current_session_context_class"&gt;${hibernate.current_session_context_class}&lt;/prop&gt;
  &lt;prop key="hibernate.show_sql"&gt;${hibernate.show_sql}&lt;/prop&gt;
  &lt;prop key="hibernate.connection.release_mode"&gt;auto&lt;/prop&gt;
  &lt;prop key="hibernate.query.substitutions"&gt;${hibernate.query.substitutions}&lt;/prop&gt;   
  &lt;!-- Do not allow transaction.manager.lookup to be defined here. --&gt;       
&lt;/props&gt;

</property>
</bean>

This doesn’t include the changes to the transaction manager or the like, but it gets the point across.

Then, since the servlet isn’t around to do it for me, I have to define a hibernate session “in-house” for integration tests:

public void onSetUp() throws Exception
{  

super.onSetUp();

ApplicationContext context = applicationContext;
sessionFactory = (SessionFactory) context.getBean("sessionFactory");
sessionFactory.getCurrentSession().beginTransaction();

}

@Override protected void onTearDown() throws Exception {

if (transaction != null) 
{
    transaction.commit();
}

super.onTearDown();

}

And now all my integration tests work! Yay!

I used Carlos Sanchez’s post as a guide, and have been looking at Alef’s Configuration Management with Spring for more information.

Still surprised at how much work it is to override properties in Spring. As far as I know, there’s no way you can explicitly say “I’m scribbling over another bean definition” now, apart from where you’ve explicitly set it up that way with PropertyOverrideConfigurer. Among other things, I don’t see any way that you can change a bean’s defined class once it’s set up, which is something you can do in Nucleus right off the bat.

Still, it works, and I should be thankful I can do integration testing at all. Now to go look at Webwork forms.

Hibernate vs. Spring 7

Posted by wsargent Tue, 17 Jan 2006 06:36:00 GMT

So. In the previous post, I talked about integration testing. An integration test runs through Webwork, Hibernate and Spring and expects a certain amount of scaffolding to work right.

I’ve been having a very tough time getting that scaffolding to work right. Part of the problem is that there is no mutually agreed pattern for handling Hibernate sessions.

When I am using the Spring design pattern (OpenSessionInView), the Spring integration tests work fine. But that pattern doesn’t allow lazy loading unless you do some fancy acrobatics. And if you write your own code to handle Hibernate sessions… things don’t work so well.

Specifically, I wrote a transaction filter and a session per request filter:

Session session = sessionFactory.getCurrentSession();
try
{

// Joins the existing JTA transaction, or creates one.
session.beginTransaction();
//boolean rollback = true;
try
{
    filterChain.doFilter(request, response);
    // commented out because JTA transaction seems to
    // do this for us...
    //rollback = false;
} finally
{
    //if (rollback)
    //    tx.rollback();
    //else        
    //    tx.commit();
}

} finally {

session.close();

}

and then just did a very simple DAO pattern that called sessionFactory.getCurrentSession() from inside the DAO. Now, if I were using Hibernate 3.0, then getCurrentSession would require a JTA transaction to be in place, and I wouldn’t be able to run an integration test unless I found a replacement J2EE container. However, I’m using Hibernate 3.1. Hibernate 3.1 does not require an existing JTA transaction to be in place for getCurrentSession(). So I should be fine, right?

Wrong. It turns out that Spring wraps all calls to the sessionFactory object and calls its own utility class instead. Every time you call sessionFactory.getCurrentSession(), Spring calls SessionFactoryUtils.doGetSession() for you. And doGetSession() checks to see if there’s a JTA transaction, and if there isn’t one… it doesn’t let you call getCurrentSession.

In fairness, this has been noted as bug SPR-1354 and fixed in CVS. But that does me no good.

So what I tried to do is setup the integration tests with a different Spring configuration. Instead of setting up the sessionFactory with JTATransactionManager and OrionTransactionLookup, I’d extract all the relevant beans and properties, and have applicationContext-jta.xml and applicationContext-jdbc.xml files only containing the relevant transaction config. The sort of thing you could do in ATG in five minutes using a Nucleus layer.

This… sort of works.

However, I’m having trouble setting up the Hibernate properties. This breaks because when I’m in the app server, I need to set transaction.manager_lookup_class to OrionTransactionLookup. But I need it not to exist for the integration testing. I can define a property with a null value, but Hibernate still sees that the key exists and complains about it.

About the only solution I can think of is to have the props map abstracted out to a properties file and see if I can swap in different properties files. (EDIT: Fixed, see here for details.)

The interception framework should be optional – it should let Hibernate fail on its own. And HibernateTemplate/OpenSessionInView stuff gets around a problem that, as far as I can tell, doesn’t exist.

I don’t get it. I thought this was supposed to be easy.

EDIT: okay, NOW I get it.

EDIT: Also see here for an excellent review of Spring’s “integration” with Hibernate.

Integration Testing with Spring 1

Posted by wsargent Tue, 17 Jan 2006 04:22:00 GMT

So far I’ve been focusing on Webwork. However, I’ve also been trying more automated testing again. My previous effort didn’t work so well, as I spent more time writing the mockobjects library than writing the unit tests, but I was happy to give it another try using TestNG.

Unit tests work fine for me, but I’m still not fascinated by them.

Things got more interesting when I discovered the integration testing classes in Spring. I found that the Spring integration test classes were built on top of JUnit, and didn’t seem to support TestNG. So I switched, and it was so worth it.

Integration testing with Spring is awesome. I can write some code, write an integration test, then run through the test until I’m sure I know what’s going on. This really helps when parsing posts. A raw post goes through several different parsers to do things like colorize Java code, bleep swearwords and escape HTML characters, so there are many opportunities to parse things in the wrong order or not at all.

Here’s a sample:

public class DisplayPostActionTest extends

    AbstractDependencyInjectionSpringContextTests {
@Override
protected String[] getConfigLocations() {
    return fakeDatabasePaths;
}

private static String[] fakeDatabasePaths = {
    "classpath*:/applicationContext-test.xml", "classpath*:/applicationContext.xml", 
};

DisplayPostAction displayPostAction;

/* (non-Javadoc)
 * @see org.springframework.test.AbstractDependencyInjectionSpringContextTests#onSetUp()
 */
@Override
protected void onSetUp() throws Exception {
    ApplicationContext context = applicationContext;        
    displayPostAction = (DisplayPostAction) context.getBean("displayPostAction");
}

public void testExecute() {        
    try {
        displayPostAction.setPostId("43");
        String returnValue = displayPostAction.execute();
        assertNotNull(returnValue);

        assertTrue(Action.SUCCESS.equals(returnValue));

        Post post = displayPostAction.getPost();
        assertNotNull(post);            
    } catch (Exception e) {
        e.printStackTrace();
        fail();
    }
}

}


This works great in the simple case where you’re using HibernateTransactionManager and the OpenSessionInView class in conjunction with HibernateTemplate. If you’re not… well, the next post is about that.

Hibernate Question 2

Posted by wsargent Sat, 31 Dec 2005 22:00:00 GMT

I have a question. Hibernate for the most part does exactly what I expect it to, and all is good. However, the elements() function doesn’t seem to be working as it should. I suspect my mapping may be off.

Is this legal?

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC

    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"&gt;

<hibernate-mapping package=”com.tersesystems.blog.dao.hibernate”>

<!–

CREATE TABLE blog_category (
category        varchar2(40)    NOT NULL,
PRIMARY KEY(category)
);    

–>

<class name=”CategoryImpl” table=”BLOG_CATEGORY”>

&lt;id name="name" column="CATEGORY"&gt;      
&lt;/id&gt;

</class>

</hibernate-mapping>

and

public class CategoryImpl implements Category
{

private String mName;

/**
 * @return Returns the name.
 */
public String getName() {
    return mName;
}

/**
 * @param pName The name to set.
 */
public void setName(String pName) {
    mName = pName;
}

public String toString() {
    return "CategoryImpl: " + mName;
}

}

Because when I do this query:

String queryString = "from PostImpl as post where :category in elements(post.categories) "

         + " and post.state = :state order by post.date desc";

Query query = session.createQuery(queryString);

query = query.setParameter(“category”, pCategory); query = query.setInteger(“state”, PostState.PUBLISH.asCode()); List posts = (List) query.list();

I get this:

2005-12-31 13:48:46,046 INFO [org.hibernate.event.def.DefaultLoadEventListener] - <Error performing load command>
org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.tersesystems.blog.dao.hibernate.CategoryImpl#
Programming, Programming]

    at org.hibernate.ObjectNotFoundException.throwIfNull(ObjectNotFoundException.java:27)
    at org.hibernate.event.def.DefaultLoadEventListener.load(DefaultLoadEventListener.java:118)
    at org.hibernate.event.def.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:75)
    at org.hibernate.impl.SessionImpl.immediateLoad(SessionImpl.java:643)
    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:59)
    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:84)
    at org.hibernate.proxy.CGLIBLazyInitializer.intercept(CGLIBLazyInitializer.java:134)
    at com.tersesystems.blog.dao.hibernate.CategoryImpl$$EnhancerByCGLIB$$6d6de9a8.toString(&lt;generated&gt;)
    at java.text.MessageFormat.subformat(MessageFormat.java:1237)
    at java.text.MessageFormat.format(MessageFormat.java:828)
    at java.text.Format.format(Format.java:133)
    at java.text.MessageFormat.format(MessageFormat.java:804)
    at com.tersesystems.blog.dao.hibernate.HibernatePostDAO.findPublishedByCategory(HibernatePostDAO.java:192)
    at com.tersesystems.blog.action.DisplayPostsAction.execute(DisplayPostsAction.java:130)</pre></div>

It’s disconcerting. I’m not even sure what the error message means.

Hibernate Session Management 4

Posted by wsargent Fri, 30 Dec 2005 08:28:00 GMT

I’m having trouble with Hibernate now. In particular, I’m having trouble with Spring and Hibernate’s idea of managing transactions. Hibernate doesn’t use the JTA for transaction management: instead, it uses a Session object that looks a lot like a UserTransaction, and is defined as that period when the objects that you get are actually connected to the database. So you call

session.beginTransaction();
Post post = getPost();
// calling post.getComments() will get real comment objects from Hibernate here... session.commit();

// You can’t call getComments() here because there’s no active session. // You’ll die with a LazyInitializationException. post.getComments();

You might think that Hibernate would open a new connection or session if it has to lazy load associations. This is a bad idea, because you can get a ton of open connections.

You might think that Hibernate would keep a session open on a thread, so you can do many operations in a single transaction. Well, it can. If you can get hold of a sessionFactory, you can call sessionFactory.getCurrentSession() and it will return you the ThreadLocal session back.

Except that I’m using a JTA transaction model in a J2EE web application. Oops.

EDIT: ARGH. I misread the error message. JTA works fine, but Hibernate needs a current JTA transaction and I didn’t have that.

You might think that Hibernate would have a servlet filter that would open up a single transaction at the beginning of a page render and close it when the page finished rendering. And, well, Spring does. Hibernate is a persistence solution, so from what I can tell, they’ve essentially washed their hands of Spring. But Spring has OpenSessionInViewFilter.

However, I can’t get OpenSessionInView to work. Here’s my code:

public String execute() throws Exception {
  // this is a Post object extracted by another action and set on this one
  Post post = getPost();
  String parsedPost = parsePost(post);
  setParsedPost(parsedPost);
}

// Get various lazy loaded objects if they exist (we only want to pull them in if we are // displaying posts) public String parsePost(Post pPost) { ... Map<String, Media> media = pPost.getMedia(); ... List comments = pPost.getComments(); }

and here’s what happens when I try it:

2005-12-29 23:38:27,515 DEBUG [com.tersesystems.blog.dao.hibernate.HibernatePostDAO] - <findPublishedPage: pPage = 0, pPageSize = 5>
2005-12-29 23:38:27,531 DEBUG [org.springframework.orm.hibernate3.SessionFactoryUtils] - <Opening Hibernate Session>
2005-12-29 23:38:27,625 DEBUG [org.springframework.orm.hibernate3.HibernateTemplate] - <Eagerly flushing Hibernate session>
2005-12-29 23:38:27,640 DEBUG [org.springframework.orm.hibernate3.SessionFactoryUtils] - <Closing Hibernate Session>
2005-12-29 23:38:27,640 DEBUG [com.tersesystems.blog.dao.hibernate.HibernatePostDAO] - <findPublishedPage: posts = [[...]>
2005-12-29 23:38:27,765 DEBUG [org.springframework.orm.hibernate3.SessionFactoryUtils] - <Opening Hibernate Session>
2005-12-29 23:38:27,765 DEBUG [org.springframework.orm.hibernate3.HibernateTemplate] - <Eagerly flushing Hibernate session>
2005-12-29 23:38:27,765 DEBUG [org.springframework.orm.hibernate3.SessionFactoryUtils] - <Closing Hibernate Session>
2005-12-29 23:38:27,765 ERROR [org.hibernate.LazyInitializationException] - <could not initialize proxy - the owning Session was closed>
org.hibernate.LazyInitializationException: could not initialize proxy - the owning Session was closed

    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:53)
    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:84)
    at org.hibernate.proxy.CGLIBLazyInitializer.intercept(CGLIBLazyInitializer.java:134)
    at com.tersesystems.blog.dao.hibernate.PostImpl$$EnhancerByCGLIB$$9066ad74.toString(&lt;generated&gt;)
    at java.lang.String.valueOf(String.java:2577)
    at java.lang.StringBuilder.append(StringBuilder.java:116)
    at com.tersesystems.blog.action.ParsePostAction.parse(ParsePostAction.java:108)
    at com.tersesystems.blog.action.ParsePostAction.execute(ParsePostAction.java:137)
    at com.opensymphony.xwork.DefaultActionInvocation.invokeAction(DefaultActionInvocation.java:283) </pre></div>

What this seems to say is that OpenSessionInView will only keep a session open in an object that extends from HibernateDaoSupport, and uses a HibernateTemplate. I tried overriding getHibernateTemplate() so I could add better logging to it, but someone’s declared it final and private. Which is unfriendly.

Theoretically the way to do this is to use the HibernateCommentDAO and have it take in a postId, but that seems silly to me. I don’t want to have to define a transaction explicitly around every action. I just want to be able to call post.getComments() anywhere in the page that I’m rendering, which I should be able to do.

I’d write my own filter, but I don’t want to mess with the existing Session support that exists. I started writing a filter behind the OpenSessionInView one and found out that I had two different instances of sessionFactory. I don’t know how the heck I managed that one.

Webwork Actions in a Nutshell 8

Posted by wsargent Fri, 23 Dec 2005 06:38:00 GMT

Today, I talk about webwork actions.

There are two parts to webwork actions. There’s the JSP, and there is the Java class. The JSP looks like this:

<ww:action id="myActionObj" name="'myActionType'"/>

For the sake of clarity, I haven’t shown how the name attribute wires through XWork to Spring: there’s a diagram in an earlier post. I don’t know why you need single quotes around it.

The Java class looks like this:

import com.opensymphony.xwork.ActionSupport;

public class MyAction extends ActionSupport {

public String execute() 
{
return SUCCESS;
}

}

A webwork action is roughly equivalent to a droplet, only instead of calling service(), it calls execute(). However, there is an important difference between droplets and actions. An ATG droplet is typically stateless and global: you define a single component in Nucleus, set up all the global references in the properties file, and pull all the session scoped and request scoped objects through request.resolveName(“myComponent”).

As best as I can tell, Webwork actions are request scoped. That is, there’s a new instance of the class created on every request. This is because instead of setting output parameters on DynamoHttpServletRequest, the properties of the action itself are used as output parameters.

This means that where you would normally do:

public void service(DynamoHttpServletRequest pRequest, DynamoHttpServletResponse pResponse)

  throws IOException, ServletException 

{

pRequest.setParameter("value", "I am an output parameter");
pRequest.serviceParameter("output", pRequest, pResponse);

}

you would do:

public String execute()
{
   setValue("I am an output parameter");
   return SUCCESS;
}

and then in the JSP page you would do:

<ww:action id="myActionObj" name="'myActionType'"/>
<ww:property value="#myActionObj.value"/>

Note that the action object hangs around at least for the lifetime of the page render. If you are using the same action multiple times in the same page, you should use different ids.

So that takes care of output parameters. How do you get input into a Webwork action?

You use the ActionContext class. This class uses ThreadLocal behind the scenes to give you access to your request and session state.

import com.opensymphony.webwork.ServletActionContext;
import com.opensymphony.xwork.ActionContext;
import com.opensymphony.xwork.ActionSupport;
import javax.servlet.http.;
import java.util.;

public class MyAction extends ActionSupport {

public String execute()
{
    // if you want raw access to the request...
    HttpServletRequest request = ServletActionContext.getRequest();
    Map httpParameters = request.getParameters();

    // ... or if you want to remove any dependency on servlets...
    ActionContext context = ActionContext.getContext();
    Map parameters = context.getParameters();

return SUCCESS;
}

}

Now, the following section I can’t speak to. Sebastiano defined the following interceptors here in xwork.xml, so I have no idea what they do or why he defined it:

   <interceptors>

  […]

  &lt;!-- a small stack used for the actions in ww:action tags --&gt;
  &lt;interceptor-stack name="simpleStack"&gt;
    &lt;interceptor-ref name="prepare" /&gt;
    &lt;interceptor-ref name="servlet-config" /&gt;
    &lt;interceptor-ref name="params" /&gt;
  &lt;/interceptor-stack&gt;
&lt;/interceptors&gt;

&lt;!--
   Anything you would normally use for display is defined with the simple stack. 
   Here, we count an array.
--&gt;    
&lt;action name="countArrayAction" class="countArrayAction"&gt;
  &lt;interceptor-ref name="simpleStack"&gt;&lt;/interceptor-ref&gt;
&lt;/action&gt;

We can then get down to counting how many items are in an array. Using this pattern, parameters can be defined explicitly:

<ww:action id="countArrayObj" name="'countArrayAction'">
   <ww:param name="'array'" value="#someArray"/>
</ww:action>

The code for this goes:

public String execute()
{

ActionContext context = ActionContext.getContext();
Map parameters = context.getParameters();

// Note that while this is a contrived example, Webwork does like to return 
// string parameters as String[], even if there's only one of them passed in.
Object[] randomParamArray = (Object[]) parameters.get("array");

int count = (randomArray == null) 0 ? randomArray.length;
setCount(count);

return SUCCESS;

}

And that’s Webwork actions in a nutshell.

Webwork and OGNL 6

Posted by wsargent Wed, 21 Dec 2005 06:38:00 GMT

I understand Webwork now. For a while I was completely blocked. The documentation on the wiki just didn’t give me enough information to really understand what was going.

Finally, I wrote to Sebastiano Pilla and asked for help. He sent me some source code that showed me how to use Webwork in practice, and I was able to use that as a model. I still don’t understand some areas of Webwork, but I understand it well enough.

What I was meaning to do was:

<dsp:droplet name="DisplayPosts">
  <dsp:oparam name="output">

 &lt;dsp:droplet name="ForEach"&gt;
   &lt;dsp:param name="array" param="posts"/&gt;
   &lt;dsp:param name="elementName" value="item"/&gt;
   &lt;dsp:oparam name="output"&gt;
       &lt;dsp:valueof param="item.title"/&gt; &lt;br /&gt;
       &lt;dsp:valueof param="item.body"/&gt;
   &lt;/dsp:oparam&gt;
 &lt;/dsp:droplet&gt;

</dsp:oparam> </dsp:droplet>

or using JSTL:

<dsp:droplet name="DisplayPosts">
  <dsp:oparam name="output">

 &lt;!-- convert between DSP and JSTL --&gt;
 &lt;dspel:getvalueof var="postz" param="posts"&gt;
 &lt;c:forEach var="item" items="${postz}"&gt;
   &lt;c:out value="${item.title}"/&gt; &lt;br /&gt;    
   &lt;c:out value="${item.body}"/&gt; 
  &lt;/c:forEach&gt;
 &lt;/dspel:getvalueof&gt;

</dsp:oparam> </dsp:droplet>

Webwork does not expose objects to JSTL by default. It says it can, but nothing happened when I tried:

<ww:action name="'displayPosts'" id="displayPostsObj"/>
<ww:set name="postz" value="#displayPostsObj.posts" scope="page"/>
<c:forEach var="item" items="${postz}">

&lt;c:out value="${item.title}"/&gt; &lt;br /&gt;    
&lt;c:out value="${item.body}"/&gt;

</c:forEach>

Webwork uses OGNL to expose data. Which means it has almost nothing in common with either JSTL objects or DSP droplets. So the correct way of doing this in Webwork is:

<ww:action name="'displayPosts'" id="displayPostsObj" />
<ww:set name="postz" value="#displayPostsObj.posts" scope="page"/>
<ww:iterator value="#postz">
  <ww:property value="title"/> <br />
  <ww:property value="body"/>
</ww:iterator>

This works. But it’s not using the same model that you would see in JSTL. Whereas DSP is scope based (params only exist in oparam tags) and JSTL is mostly flat, OGNL is stack based. So when you iterate over a collection, it takes the element and puts it onto the value stack. When you access properties without the # syntax, it assumes you’re talking about the first element on the stack. So <ww:property value=”title”/> gets item.title, and so on. If anything, it’s like $_ in Perl. It’s a little bit of syntax magic.

But what if you want to get the item itself? Well, then you have to understand OGNL. Webwork itself doesn’t document OGNL: you have to go to the OGNL web site to do that.

<ww:iterator value="#postz">
   <ww:set name="post" value="[0]"/>
   Post: <ww:property value="#post"/>
</ww:iterator>

In OGNL, [0] returns the item itself. I don’t know why.

The # syntax can again be understood by reference to Perl: any time you try to get something that isn’t on the default stack, you have to specify it with # beforehand to indicate it’s a variable. There are a number of variables that are defined in Webwork; #parameters, #application, and so on. If you have http://example.org/myapp/index.jsp?foo=bar and you call <ww:property value=”#parameters.foo”/>, you can get “bar”. Instead of <%= request.getContextPath %>, you can do <ww:property value=”#request.contextPath”/>.

The date formatting support in Webwork is threadbare. They suggest that you use the <ww:text> tag, but that requires that you parameterize it into a ResourceBundle. There’s nothing like a DSP tag converter as far as I can tell. The most effective way of formatting a date I’ve found is:

<%@ taglib uri="webwork" prefix="ww" %>
<%@ page import="com.opensymphony.xwork.util.OgnlValueStack" %>
<%@ page import="java.util." %>
<%@ page import="com.tersesystems.blog.util." %>
Assume the code's inside an iterator and timestamp is a property of the element...
<%
  OgnlValueStack stack = (OgnlValueStack)request.getAttribute("webwork.valueStack");
  Date date = (Date) stack.findValue("timestamp");
  String formattedDate = DateUtils.format("M/d/yy h:mm aa", date);
%>

(If you do something like this yourself, make sure you get the synchronization right. DateFormat isn’t thread-safe.)

If you want to do something more complex in OGNL, you need to know the internal model down cold. Say that you want to dereference an element inside a map using a specific key in Webwork. You’d think that using the [0] syntax described earlier would work:

<ww:iterator value="#myObject.keyList">

&lt;ww:set name="element" value="#myObject.map[[0]]"/&gt;

</ww:iterator>

But this doesn’t work. (Nope, still not sure why.) The way to do this is:

<ww:iterator value="#myObject.keyList">
  <ww:set name="element" value="#myObject.map[top]"/>
</ww:iterator>

I found this from the forums. I don’t know what “top” means. I imagine it’s some special syntax that tells OGNL to peek at the stack, but I can’t see it documented anywhere. (EDIT: docs here. Apparently it’s part of XWork.)

OGNL has interesting type conversion as well. For example, you set pages=3 as a query parameter. You’d expect #parameters.pages[0] - 1 == 2, and it does. But #parameters.pages[0] + 1 == 31. The only way I found to get around this was to cast to an int by hand, which meant:

<ww:if test="#displayPostsObj.page.nextPage == true">
 <ww:url id="pastURL" value="/index.jsp"><ww:param name="'page'" value="@java.lang.Integer@parseInt(#pageNum) + 1"/></ww:url>
 <a href="<ww:property value="#pastURL"/>">Past</a>
</ww:if>

That’s enough for now about Webwork syntax. Next post, I go into Webwork actions.

Hitchhikers 3

Posted by wsargent Wed, 08 Dec 2004 20:14:00 GMT

I come across some strange things on teh interweb, but I didn’t expect this. I write one random post on Teen Titans and the comments page picked up some kids.

They’re a little foulmouthed, but they’re pretty cute.