cocoon-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Ovidiu Predescu <ovi...@cup.hp.com>
Subject Re: [RT] Managing Flow and Resources
Date Mon, 10 Dec 2001 22:11:48 GMT
On Mon, 10 Dec 2001 10:56:39 -0500, Berin Loritsch <bloritsch@apache.org> wrote:

> Berin Loritsch wrote:
> 
> > Torsten Curdt wrote:
> > 
> 
> >>> To be able to incorporate URLs to older points in the processing, the
> >>> send-response() function could return as value an object that contains
> >>> the URL to its continuation. You can pass this URL in the optional
> >>> dictionary, as an additional argument, if you wish so.
> >>>
> >>> function addNumbers(HttpRequest request)
> >>> {
> >>>  send-response("first-page.xml", "my-pipeline");
> >>>  first = request.getIntParameter("first");
> >>>  send-response("second-page.xml", "my-pipeline", {"first" = first});
> >>>  second = request.getIntParameter("second");
> >>>  result = first + second;
> >>>  send-response("third-page.xml", "my-pipeline", {"result" = result});
> >>> }
> 
> 
> Having looked at this example, something doesn't sit right with me.  This
> has to do with the fundamental URL contracts.  This "flow" is managed within
> one URL.  For instance, it could be mounted at "http://foo.com/add".  It
> would behave like this:
> 
> URL: http://foo.com/add
> PAGE:
> 
>      Enter First Number: _________
>      [Submit]
> 
> 
> 
> URL: http://foo.com/add
> PAGE:
> 
>      Enter Second Number: _________
>      [Submit]
> 
> 
> 
> URL: http://foo.com/add
> PAGE:
> 
>      The result is: _________
>      [Done]

Because of the continuations, the URLs for the second and third pages
would probably look more like:

http://foo.com/add/4ffds8454mfd90

with the last part identifying the continuation object.

This is similar with the way WebObjects does it. I'm open to
suggestions though on a better scheme to encode the continuation
identifier.

> While this is a possibility, and might be helpful when you only want one
> entry point for a form process, it does not address options when you have
> multiple entry points.  Nor does it address content to url issues.  If we
> want the flow to be managed externally, we must have a method to either
> plant the target in the pipeline, or use redirects.  Niether are very clean.
> 
> In the first approach (embedding targets in the pipeline) we would
> have to add a <parameter name="target" value="second-page.html"/> to
> embed in the form.  I find this to be acceptable when you don't want
> to depend on the redirection mechanism in a server--or when it is
> well known what the next page is going to be.  This is in cases
> where there is only one "next page".
> 
> In the second approach, you must depend on the servers
> implementation of redirects (while supposedly standard, I have run
> into some issues).  There are also different type of redirects (a
> host of 30X HTTP response messages).  None of them really supports
> the notion of "normal" navigational flow between form pages--though
> _many_ systems (like ColdFusion) routinely use them to manage form
> flow.
> 
> For instance, with ColdFusion, it is concidered good practice to
> have two templates per form page.  The first template is to display
> the form, and the second to simply process it.  When the second is
> done, it simply redirects the user to the next page.  I don't like
> this because it artificially polutes the URI space with intermediate
> targets who's sole responsibility is to point you to another
> resource.
> 
> In Cocoon the only way to manage the URI space as well as processing
> is to use redirects and value substitution.  For instance, I would
> much rather have a form explicitly say it's next page if it does not
> depend on user input like this:
> 
> <form action="{target}"/>
> 
> The target is chosen by the flow manager, and given to the form so
> no redirection has to take place.  This is better IMO than forcing
> the use of redirects for these simple cases.
> 
> What about multiple targets from one form?  In those cases, it is
> much more desirable to use redirections after we service the
> request.  Using the psuedo-markup I expressed earlier the concepts
> could be stated like this:
> 
> <functions>
>    <function name="addNumbers" pipeline="my-pipeline">
>      <send-response page="first-page.xml">
>        <parameter name="target" value="second-page.html"/>
>      </send-response>
>      <call-action name="getFirst"/>
>      <validate>
>        <on-error>
>          <redirect-to "first-page.html"/>
>        </on-error>
>      </validate>
>      <send-response page="second-page.xml">
>        <parameter name="first" value="{first}"/>
>        <parameter name="target" value="third-page.html"/>
>      </send-response>
>      <call-action name="getSecondResult"/>
>      <send-response page="third-page.xml">
>        <parameter name="result" value="{result}"/>
>      </send-response>
>    </function>
> </functions>
> 
> This is not necessarily clean looking at it though.

If you have a form with multiple entry points, you just split the
function that handles the navigation in multiple functions:

function first-page(request)
{
  send-response("first-page.xml", "my-pipeline");
  second-page(request);
}

function second-page(request)
{
  send-response("second-page.xml", "my-pipeline");
  third-page(request);
}

function third-page(request)
{
  send-response("third-page.xml", "my-pipeline");
}

You can then mount each function at different URLs:

http://foo.com/first/

        serves first-page.xml

http://foo.com/second/

        serves second-page.xml

http://foo.com/third/

        serves third-page.xml

If a user starts filling in the form at the first page, he/she will
see the following sequence of URLs:

http://foo.com/first            for the first page
http://foo.com/first/adb43432   for the second page
http://foo.com/first/432jfw9    for the third page

This is because the navigation started from the first-page() function,
and logically all the computation starts as a result of the initial
invocation of the first-page() function.

If instead the user starts from the second page in the form, he/she
will see the following sequence:

http://foo.com/second           for the second page
http://foo.com/second/4312ufd8  for the third page

This is the similar with the first navigation I described above,
except that the entry point in the computation was the second-page()
function.

I would say that this approach handles both the issues you mentioned
above, the multiple entry points in a form, and the content to URL
mapping.

When you have forms whose result can branch the flow, you can use the
control flow statements, like "if", "while" etc. Here's a non-trivial
example of continuations at work.

function shopping-cart()
{
  set-default-pipeline("my-pipeline");
  send-page("show-shopping-cart.xml");
  operation = request.getParameter("operation");
  if (operation == "continue-shopping")
    send-page("continue-shopping");
  else if (operation == "buy")
    buy();
  else if (operation == "update-quantities")
    update-quantities();
}

function buy()
{
  credentials = get-user();
  ship-to-address(credentials);
  charge-credit-card(credentials);
}

function get-user()
{
  // get-user() returns only with valid credentials.
  // If no valid credentials are found, this function never returns.

  for (count = 0; count < 3; count++) {
    send-page("get-username.xml");

    user = request.getParameter("username");
    passwd = request.getParameter("password");

    usersDB = UsersDatabase.newConnection();
    if (!usersDB.isUserKnown(user, passwd))
      registration();
    else
      return userDB.credentialsForUser(user, passwd);
  }

  // This shows the user a sorry page with a single link that points to Home.
  // This is an example of a non-local exit, where the computation will
  // continue at a different location in the program.
  send-page("sorry-page.xml");
}

function ship-to-address(credentials)
{
  send-page("ship-to-address", "my-pipeline",
            {"address" = credentials.getAddress()});
  change = request.getParameter("change");
  if (change)
    return change-address(credentials);
  else
    return credentials.getAddress();
}

function registration()
{
  // Register a new user
  send-page("register.xml");

  while (true) {
    username = request.getParameter("username");
    if (usersDB.isUserKnown(username))
      send-page("user-already-known.xml", {"username" = username});
    else
      break;
  }

  passwd = request.getParameter("password");
  usersDB.registerNewUser(username, password);

  send-page("registration-successful.xml"});
}

function change-address(credentials)
{
  // Function to change the address. Returns the address the user chose
  ...
}

function charge-credit-card(credentials)
{
  // Charge the credit card of the user
  ...
}

function update-quantities()
{
  ...
}

This example shows how Java objects are used to perform critical
functions like connecting to the user's databases, checking for new
users and registering them.

Also look closely at get-user() and registration() functions. The idea
is to present the user with the option of registering himself/herself
if they don't have an account. After the user registers, you want the
processing to continue where it was interrupted, e.g. you want the
registration() function to return to get-user(), right after the call
to registration(). Since the send-page() function passes to the
processing pipeline in the environment the URL to its continuation, by
clicking on that link the user effectively returns from registration()
back into get-user().

Another scenario is when you don't want the user to return to the
current computation. In that case you put links in the generated pages
that point back to other functions. They will be invoked through the
sitemap as usual, starting a new stack frame on the server side.

Notice how easy is to handle errors with this approach. In get-user()
for example, the user is asked to login into the system. If the user
isn't registered yet, he/she can choose to register. If the user
doesn't successfully register, he/she is sent to a page where he/she
cannot return in the flow of the program (the user can always use the
"Back" button to go back and complete the registration
successfully). There is no need in the buy() function to handle an
unsuccessful registration.

> The concept of "continuations" while a flexible and powerful concept must
> come to grips with the concept of branching flows--which I do not think
> it does.
> 
> I think that full FSM terminology is overkill, and would only serve to
> frighten newbies.  However, digging through the archives, I came up with
> a concept that would also work:
> 
> <flow name="addNumbers" start="first-page">
>    <resource name="first-page">
>      <pipeline type="my-pipeline" source="first-page.xml"/>
>      <next name="second-page"/>
>    </resource>
>    <resource name="second-page">
>      <action type="getFirst">
>        <pipeline type="my-pipeline" source="second-page.xml"/>
>        <next name="third-page"/>
>      </action>
>      <redirect-to resource="first-page"/>
>    </resource>
>    <resource name="third-page">
>      <action type="getSecondResult">
>        <pipeline type="my-pipeline" source="third-page.xml"/>
>        <done/>
>      </action>
>      <redirect-to resource="second-page"/>
>    </resource>
> </flow>
> 
> This uses the same semantics for actions and provides the transitions,
> handling errors.

I don't like the above semantic. The redirect functionality was
invented to alleviate the lack of continuing an interrupted processing
that can change the path in your program. I think there's no need for
it with the model I propose. Maybe I'm wrong, please show me if this
is the case.

Also I don't like to express logic in XML, it's not what it was
designed for. We need a programming language, so why use an XML syntax
to express this? The attempts to use XML to implement a programming
language are taking XML too far, where it wasn't designed to work.

-- 

I think with the new model, there's no need for actions, redirects and
other things that the sitemap currently has. They are no longer needed
as you have full control over what's going on in your application at
the program level. And you can control your application in a much
better way than before.

In the new model, the sitemap becomes a dispatcher to either a
pipeline, or to a function in our language with implicit continuations
(any good name for it?).

A pipeline describes how an input document should be transformed to
HTML/WML etc, and nothing else. No actions, no redirects, no
programming at the pipeline level.

If you need to describe logic, you do it using the language. This
describes which pages are sent in what order. When you want to
generate a result, you call send-page() with the source document, a
pipeline definition that should process your document, and additional
data to be used in generating the source page dynamically.

To generate the source document, it's better if we use a markup
language that prevents users from doing computations in the page. The
page should be generated only from data passed to the pipeline by
send-page(). I haven't used it before, but Velocity seems to be a good
fit for this, as it doesn't allow any embedded processing in the page.

With this model we have the clear separation of concerns. Each layer
does only the things it's supposed to do, in a clean way.


Best regards,
-- 
Ovidiu Predescu <ovidiu@cup.hp.com>
http://orion.rgv.hp.com/ (inside HP's firewall only)
http://sourceforge.net/users/ovidiu/ (my SourceForge page)
http://www.geocities.com/SiliconValley/Monitor/7464/ (GNU, Emacs, other stuff)

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


Mime
View raw message