Controller Mockup

This design equates Controller=Module/dispatch action modeled after Beehive. It features:

The latter two points require some explaination. First, following the design of beehive, validation annotations should be able to be defined in multiple places: the controller class, the action method, or the property getter. Since Beehive supports shared flows, perhaps those would be used to share validation form definitions among other things. In this mock, I like the ability to define a quick validation properties without bothering with the overhead of a full form. While I'm sure in actual use the annotations would have to support more complex definitions, I perfer them to be as minimal as humanly possible.

Second, since most Actions have only one forward, I think it makes sense to default a success outcome (encouraging the use of the static field) to a page containing the name of the action. Beehive gives each Controller its own prefix path, mapping to a physical path on the hd used when resolving jsp's. If the success result is returned and no forward defined, I think the name of the action should be provided to the default result type (jsp, velocity, etc) to guess the name of the file. So, for the login action in the "" or default Controller, the jsp result type would guess {appRoot}/login.jsp. This is similar to how Ruby on Rails operates.

Finally, I'm warming to how Beehive separates Controller.java files from the main source and includes them right along side the jsp's. I think it would be interesting to go farther and, in development mode, leave them there for deployment and include a compiling classloader that compiles on file changes. Cocoon has already done the legwork for this feature. This would go a long way to encourage rapid development.

Terminology

Why XDoclet?

I like XDoclet for the default config/annotation engine for several reasons:

That said, I still think we would need to create Java 5 annotations as they have several key benefits, not the least being compile-type error checking, but I think supporting Java 1.4 is more important personally.

Controller.java Mockup

Following Beehive, this code is located in {approot}/Controller.java. Since it is the default package, it's actions will be called from the root, for example, the "index" action will be called by {contextPath}/index. If it was in the 'foo' package, it would be located at {approot}/foo/Controller.java and called by {contextPath}/foo/index.

import com.opensymphony.xwork.Action;
import com.opensymphony.xwork.ActionContext;
import java.util.Map;
import com.mycompany.app.UserManager;

public class Controller {

    /** @ti.action */
    public String index() {
        return Action.SUCCESS;
    }    
    
    /** @ti.action */
    public String login() {
        return Action.SUCCESS;
    }

    /**
     * @ti.action
     * @ti.validateRequired userName "User name is required"
     * @ti.validateRequired password "Password is required"
     *
     * @ti.forward name="success" type="redirect" value="index"
     * @ti.forward name="error" type="action" value="login"
     */
    public String processLogin() {
        ActionContext ctx = ActionContext.getContext();
        Map params = ctx.getParameters();
        String userName = (String)params.get("userName");
        String password = (String)params.get("password");

        if (ctx.getMessages().size() == 0 && UserManager.isValid(userName, password)) {
            return Action.SUCCESS;
        } else {
            ActionContext.getInstance().put("error", "Invalid login");
            return Action.ERROR;
        }    
    }

    /**
     * Demonstrates login action with POJO form
     * @ti.action
     */
    public String processLoginWithForm(LoginForm form) {
        // do something
        return Action.SUCCESS;
    }
 
    /**
     * POJO form with validation annotations on fields.
     */
    public static final class LoginForm {
        
        private String userName;
        private String password;

        public void setUserName(String name) {
            this.userName = name;
        }

        public void setPassword(String val) {
            this.password = val;
        }

        /**
         * @Ti.validateRequired "User name is required"
         */
        public String getUserName() {
            return this.userName;
        }

        /**
         * @Ti.validateRequired "Password is required"
         */
        public String getPassword() {
            return this.password;
        }
    }    
        
}

Comment by rich on Tue Jul 5 15:08:28 2005

I think that JSR175-style annotations should be prime, rather than the reverse. XDoclet-style annotations are definitely cleaner, but tool support will always end up getting built around the standard ones. Aside from the fact that editors will become friendly to raw annotations (which I believe to be true, even beyond the current support for statement completion), annotation support is already being built into the Eclipse JDT, so higher-level tools (design surfaces etc.) will have access to them more easily than they would to XDoclet tags.

I'd be happy to have these two goals (XDoclet/JSR175-style annotation support) be peers, and in fact, there's a typesystem in Beehive that can run on top of both. I just think it would be a mistake to make tool-friendly annotations a secondary goal.

Comment by rich on Tue Jul 5 15:19:25 2005

I really like the defaults behavior -- it eliminates a lot of rote code. One comment on this is that returning String is pretty limiting. It's the approach JSF took, and it's a roadblock if you ever want to attach something programmatically to the result. We can wait to see if we have a use for it, but we might end up wanting something like Forward in Beehive. My ideal would be to have String and a complex object both be valid return types.

Comment by rich on Tue Jul 5 15:20:41 2005

Shouldn't there be an annotation to denote an action? I think it would be bad to have any public String getter turn into a user-addressable action. Conversely, I think it would be bad to say that no action can ever start with 'get'. Thoughts?

Comment by mrdon on Tue Jul 5 16:29:34 2005

I agree both xdoclet and jsr 175 style annotations should be peers. The one feature of jsr 175 annotations that bothered me is you weren't allowed to repeat an annotation (i.e. multiple forwards) which forced you to shove everything into a giant annotation. Any way to minimize that?

Regarding return types, I'm not sure how that would work with xwork, but we can look into that. Is there a particular usecase you are thinking of?

Regarding action annotation, good point. How is this solved in JSF? I think a simple @action marker annotation would do the trick nicely, as much as I hate to require a default annotation :/

Comment by rich on Tue Jul 5 23:26:39 2005

1) I agree -- the JSR175 restriction on repeating annotations is terrible. If not for that, the annotations actually wouldn't be so bad... just an extra set of parentheses. I don't know of any way to minimize that pain (except through a nice hierarchical editor). I think that people who use editors will stick with JSR175, and people who compose and edit by hand will consider XDoclet. But, if there are good editors... I bet the former group will dwarf the latter.

2) The main usecase I was thinking of is the mechanism for passing initialization data to the view. Separating this kind of thing out from more general means (like request attributes in Servlet land) helps if you want to preserve non-long-lived state for 'go-back' situations... returning to a page that had validation errors, coming back out of a nested flow, etc. It also helps from the tool angle when there are ways to declare types to go along with the actual data that's being passed. In Beehive there are constructors and setters on Forward for passing initializer form beans and "action outputs":

    return new Forward("success", new LoginForm(...));

or

    Forward fwd = new Forward("success");
    fwd.addActionOutput("initData", ...);
    return fwd;

etc. There are also optional annotations for declaring the types and required/optional flags to go with the actual data. Assuming this sort of thing is useful in Ti (I think it is, but we'll see :) ), we could either accept both String and some complex type, or we could decide that return new Forward("foo") is simple enough. In the latter case I think Action.SUCCESS|ERROR would still always be a valid return value.

3) In JSF, the method "actions" aren't user-addressable, so they didn't run into the same issue. Components bind to methods through the EL, e.g., <h:commandLink action="#{someScope.myBean.login}" .../>, which resolves to method login() (not getLogin()... funny muddling of property- and method-binding).

Comment by mrdon on Wed Jul 6 09:16:01 2005

  1. Well, if we stick to using them for generating xml configuration at build-time, then it won't take much extra work to support both. For instance, the code in svn now maps the xdoclet tags into xwork xml, re-using their configuration system.
  2. Hmmm...it wouldn't be hard to accept both String and Forward returns, and we could stick code between our action invocator and the controller to properly process the Forward. I think the question is if two techniques are more confusing to the user. On one hand, returning String keeps in line with JSF and WebWork2, but as you point out, the other adds additional functionality. Hmm...

  3. Oh right, requests are for pages and, at least it used to be, everything is a POST. I do prefer requests being for actions, but yes, we will have to probably add that marker annotation then.

Comment by rich on Wed Jul 6 20:28:42 2005

1) OK, sounds good. I'd suggest then that we focus first on the runtime, with handcoded xwork configs. We can assume that annotation/tag processing is a (large) implementation detail. It's definitely the part I have the fewest questions about. What do you think?

2. One other thought: if there's always a context available, some of the stuff that's done through Forward in Beehive could be done on the context instead. Maybe we should start with String and operate under the assumption that the context would be used for everything else?

Comment by mrdon on Thu Jul 7 09:07:05 2005

  1. Well, actually, I have already written and tested a tag processor and ant task that uses xdoclet's xjavadoc and velocity to easily generate the xwork config, but you are right we shouldn't focus on it yet.
  2. Again, already implemented regarding use of Spring. :) I'm not exactly following how that relates to the context, and by context I think you mean chain WebContext?

Comment by rich on Thu Jul 7 16:23:31 2005

1) Yeah, I saw that. You've been busy. :) I just figured that we'd end up stuffing a lot more into the config files than exists now. The processing layer in Beehive is large, because there's so much checking that can be done in the annotations and between annotations and types/methods/fields (which is a real advantage to annotation processing over XML configuration).

2) I'm confused. Are we having a String vs. Spring mismatch here? I just meant that we could use whatever context we provide (extension of WebContext?) to store what Beehive stores in the Forward object. So the action methods could return Strings. Instead of Springs. :)

Comment by mrdon on Thu Jul 7 16:34:40 2005

  1. Yep, good point, however, I'm hoping the velocity template will be easy enough to edit, but if it starts to absorb too much time, I agree it can wait.
  2. Doh, I read 'Spring'. I agree returning Strings is a better design than Springs :) Also agree we could move that into a context. I'm thinking we'll need to create a ControllerContext, much like Struts 1.x's ActionContext, which will wrap xwork's ActionContext which will have chain's WebContext. Quite the Context party...

Comment by rich on Thu Jul 7 17:22:56 2005

2) Yeah... I guess Context parties are the wave of the future. :)

Comment by rich on Thu Jul 7 17:37:11 2005

4) Hey, how would people feel about making the XDoclet-style annotations ordered according to hierarchy? In the current mockup, the @Ti.action annotations would come before all the others on a method. This would allow there to be a better correspondance between XDoclet-style and JSR175-style annotations (and I don't think it's a harsh requirement).

5) Minor, but it's nice to settle on things like this early: I'd be in favor of lowercasing all elements of the annotation names, or uppercasing them all: @ti.action or @Ti.Action. Are XDoclet tags usually lowercased? On the JSR175 side, action is a type (an @interface), so it would seem strange to lowercase it if the wrapper interface (Ti) was uppercased.

Comment by mrdon on Thu Jul 7 18:05:37 2005

Both suggestions sound good and pick one - upper-cased tags or lower-cased.

Comment by rich on Thu Jul 7 22:08:03 2005

Cool. :) I like @ti.action because it's easier to type...

Comment by rich on Wed Jul 13 16:27:26 2005

I wasn't reflecting hard enough on the Forward-vs-String question. I think we'll need to at least support something like Forward if we want to be able to accept dynamically-generated URIs. Of course we could support String and URI, but this seems like a low-flexibility option.

Comment by mrdon on Wed Jul 13 16:48:18 2005

Not necessarily - WebWork allows the location attribute (Struts' ActionForword 'path') to be an OGNL expression. If we supported pluggable expression languages, we could allow the language evaluation engine to process 'location' providing dynamic paths.

Comment by rich on Sat Jul 16 15:55:16 2005

Say I'm in an action method and I have:

In that case, what do I return in order to forward or redirect to that URL? And how do I specify that it's a forward or redirect?

Comment by mrdon on Sat Jul 16 16:45:35 2005

I don't know what WebWork2 would suggest, but I'd imagine you'd define two forwards, one a dispatch the other a redirect, which pulls the location out of the context/request attribute. While obviously a page like that isn't toolable, you can at least tell there will a redirect and a normal dispatch as results.

Comment by rich on Mon Jul 18 20:40:03 2005

Would the method need to stick the url into a context/request attribute directly? So I understand, could you show what the action method would look like?

If we can stick with String, that's good as long as there's not too much arcane knowledge required to fit everything in...

Comment by mrdon on Tue Jul 19 10:53:16 2005

Yes, you'd have:

ActionContext.getContext().put("url", url);

then as your forward:

@ti.forward name="dynamicUrl" location="/public/#{url}"

Of course we would use the standard JSP 2.0 EL, but the principle is the same.

Comment by rich on Tue Jul 19 13:20:43 2005

Hmm... OK. I do like that there's an identifiable @ti.forward, although the mechanism is difficult to discover. Much harder than recognizing that there's a Forward constructor which takes URI. I agree with trying this out, though...

StrutsTi/ControllerMock (last edited 2009-09-20 23:12:06 by localhost)