struts-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Apache Wiki <wikidi...@apache.org>
Subject [Struts Wiki] Update of "StrutsTi/ControllerMock" by DonBrown
Date Wed, 31 Aug 2005 05:07:00 GMT
Dear Wiki user,

You have subscribed to a wiki page or wiki category on "Struts Wiki" for change notification.

The following page has been changed by DonBrown:
http://wiki.apache.org/struts/StrutsTi/ControllerMock

The comment on the change is:
Adding controller mock discussion

New page:
#format wiki
#language en
= Controller Mockup =

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

 * No configuration - everything done through annotations
 * XDoclet annotations to allow for Java 1.4 as well as, IMO, easier to read annotations
 * Option to use Form object or not
 * POJO controller
 * Validation annotations available, even without form object
 * Result forwards not required to be defined, when Action.SUCCESS returned, defaults to action
name.

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 ==
 * ''page flow controller'': the controller class for a particular module path
 * ''page flow'': an entire "package" of a controller and its associated view elements
 
== Why XDoclet? ==

I like XDoclet for the default config/annotation engine for several reasons:
 
 * The annotations are cleaner
 * All annotations will be used at build time to generate an XML file, not needed at runtime.
 While Java 5 does provide APT, it is a sun-specific tool and, at least currently, cannot
be invoked by Ant, not to mention it requires Java 5 annotations.
 * Since the annotations create an XML file, that is one less XWork Configuration implementation
to create so we get two config styles for free.  I wonder if we could use a properties file
to generate XML as well making our job that much easier.
 * Able to include in the Struts Ti distro I believe and currently used by many Struts developers

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.
 1. 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:
    String url = getSomeURL();

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...

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@struts.apache.org
For additional commands, e-mail: dev-help@struts.apache.org


Mime
View raw message