Last time I wrote an RT about these things, the flowmap wasn't implemented. Today it's working, but there are things that I would like to change.
This RT is to start a discussion that will hopefully lead to a coherent and community-driven design on what the flowmap engine will look like. First of all, since there is no clear howto about the current flowmap and I need to give you context in order to make you partecipate in the discussion, I'll write a basic explaination of what's going on. - oooo - How Ovidiu implemented the flowmap concept ------------------------------------------ First of all, do a 'cvs checkout' of HEAD if you haven't done so. I'm referring to the samples that you find in /xml-cocoon2/src/webapp/samples/flow/examples/calc/ which is a sample about a very simple web application that implements a calculator using the flowmap. First of all, I'll outline the 'procedural flow logic' of this application using highly-pseudo code: 1) tell me the first operand 2) tell me the second operand 3) tell me the operation to apply 4) here is your result You'll see how much similar to the actual implementing code this will look like. This is probably already a great advantage over anything that we had before. Let us look at the sitemap: <map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0"> [skipping component declaration] <map:resources> <map:resource name="flow"> <map:script src="calc.js"/> </map:resource> </map:resources> <map:pipelines> <map:pipeline> <map:match pattern="kont/*"> <map:continue with="{1}"/> </map:match> <map:match pattern=""> <map:call function="calculator"> <map:parameter name="prefix" value="/samples/flow/examples/calc/"/> </map:call> </map:match> <map:match pattern="*.html"> <map:generate src="{1}.xsp" type="serverpages"/> <map:serialize/> </map:match> </map:pipeline> </map:pipelines> </map:sitemap> So, Ovidiu added three concepts that weren't found in the previous sitemap implementations: 1) a new "map:script" component connects a sitemap resource with a flowmap (which is here implemented as a javascript program) 2) a new attribute "map:call/@function" allows you to pass control from the sitemap to the flowmap, calling the function defined in the attribute and passing the nested "map:parameter" to the function as a regular call. 3) a "map:continue" element indicates that the flow must be continued with the continuation passed in the attribute "@with". Let's look at the flowmap: var prefix; function calculator(uriPrefix) { prefix = uriPrefix; var a = getNumber("a"); var b = getNumber("b", a); var op = getOperator(a, b); if (op == "plus") sendResult(a, b, op, a + b); else if (op == "minus") sendResult(a, b, op, a - b); else if (op == "multiply") sendResult(a, b, op, a * b); else if (op == "divide") sendResult(a, b, op, a / b); else sendResult("Error: Unkown operator!"); } the 'calculator' function is the one that is called from the sitemap. As you can see, the procedural logic of your flow is *very well* described: I'm sure that it would takes a few minutes for anybody to understand what your application is doing. This is *NOT* possible with any other FSM-based approach. If you think about it, in Cocoon 1.x we used PI to encode state transitions in every stage, then we identified this as a problem and we centralized it into the sitemap: the location where you can understand what your URI does in a central and confortable location. Here we are doing it again, but instead of contralizing declarative client/server behavior, we are centralizing procedural web-app flow. But let's keep looking into the flowmap: function getNumber(name, a, b) { 1 var uri = prefix + "getNumber" + name.toUpperCase() + ".html"; 2 sendPage(uri, { "a" : a, "b" : b }); 3 return parseFloat(cocoon.request.getParameter(name)); } This function collects the number required by the flow: line 1: the URI of the resource to use is created line 2: the URI is sent to the client, along with some parameters in a map line 3: a float is returned from the cocoon request parameter named with the given name Anybody who ever wrote a webapp would think we are nuts: line 2 generates a response and line 3 reads a request. But here is where the flowmap gets magic, there are bunch of things that you don't see happening. So, let's look at that function from what really happens behind the lines, let us suppose we call var a = getNumber("a"); then a) the sitemap is called to process a the URI 'getNumberA.html' b) the sitemap matches with this matcher <map:match pattern="*.html"> <map:generate src="{1}.xsp" type="serverpages"/> <map:serialize/> </map:match> c) the sitemap executes the "getNumberA.xsp", which is given by <xsp:page language="java" xmlns:xsp="http://apache.org/xsp" xmlns:jpath="http://apache.org/xsp/jpath/1.0" > <document> <body> <s1 title="Calculator"> <form> <xsp:attribute name="action"> <xsp:expr>"kont/" + <jpath:continuation/></xsp:expr> </xsp:attribute> <p>Enter value of <strong>a</strong>: <input type="text" name="a"/></p> <input type="submit" name="submit" value="Enter"/> </form> </s1> </body> </document> </xsp:page> where the "jpath" logicsheet is used to obtain the "continuation" of the current flow and is then encoded in the URI called. So, when the users hits 'submit', Cocoon will receive a request for an hypotetical URI "kont/39849834983498", then Cocoon will match it with: <map:match pattern="kont/*"> <map:continue with="{1}"/> </map:match> and resurrect the flow logic from where it was left. So, again, between function getNumber(name, a, b) { 1 var uri = prefix + "getNumber" + name.toUpperCase() + ".html"; 2 sendPage(uri, { "a" : a, "b" : b }); 3 return parseFloat(cocoon.request.getParameter(name)); } line 2 and line 3, several things happen 1) control is given to the sitemap 2) the sitemap produces a response which encodes the continuation 3) this response is sent to the client 4) the client acts with the response 5) cocoon receives a request for a continuation-decoding URI 6) the flowmap is called with the given continuation (think of it as a starting point) If you think of the above as a command line environment, line 2 gives you the dialog where to enter the number and line 3 uses the number returned from the user. [the rest of the flowmap is easy to understand if you understood so far, so I'll skip it] - oooo - The problems with this approach ------------------------------- First of all, let me say that I consider the above concept the biggest advancement in server-side web technology since servlets. This design not only makes it a breeze to implement MVC, but it shows you how natural and clear things become once you separate the procedural flow, from the declarative serving of resources. At the same time, I think there are a number of issues that we might solve before freezing the concept in a beta release: 1) the semantics to call a flowmap from a sitemap are too implicit: users must assume to much from what they read (ie self-readability of the concept is very poor). 2) the concept of continuations is not transparent, there is concern overlap between the view designers and the sitemap managers since the view designers must be aware of the URI location of the continuation-decoding URI. [NOTE: it is *not* required that you use continuations for your flow logic, you can use whatever means you have to control state as you did previously, such as REST-like passing style, sessions or cookies] 3) there is currently only one way to implement the MVC "view" and that forces you to use XSP. This might not be a performance problem, but it might become a usability problem. Let's talk about each one of them independently. [NOTE: Giacomo and I spent a full evening and the next day (during "Italy vs. Mexico"! consider that!) talking about these things, so the above reflects design concepts of both of us] More explicit sitemap markup ---------------------------- Here is what I think would be a much better approach to connect a sitemap and a flowmap: <map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0"> <map:flowmaps default="calculator"> <map:flowmap name="calculator" src="calc.js" language="javascript"/> </map:flowmaps> <map:pipelines> <map:pipeline> <map:match pattern=""> <map:call function="calculator('prefix','/kont')"/> </map:match> <map:match pattern="kont/*"> <map:call with-continuation="{1}"/> </map:match> <map:match pattern="*.html"> <map:generate src="{1}.xsp" type="serverpages"/> <map:serialize/> </map:match> </map:pipeline> </map:pipelines> </map:sitemap> There are a couple of major differences: 1) a flowmap isn't a sitemap resource, but it's something else. The above semantics reflect this. 2) you can have more than one flowmap in a sitemap (then you can define a default one or use a 'flowmap' attribute in "map:call" to identify which flowmap you are calling) [of course, in order to accomplish this, the implementation must completely isolate the different flowmaps and its global variables] 3) the "map:call" element is used to call sitemap resources (pipelines) and flowmap resources (functions), an attribute is used to indicate the behavior that should be used and it's consistent with the rest of the sitemap. 4) the "map:call" element can use both nested map:parameters or directly using the 'function(param, param)' syntax inside the attribute. This makes it more readable in some cases. Making continuations transparent -------------------------------- I personally don't have a clear view of the usability of this concept myself: I like it very much, it's clear and consistent and it's similar to the session concept. The only thing is that we might provide a "ContinuationURLEncodingTransformer" of some sort to separate the concern of those who write the forms and those who write the sitemap, even if, passing the 'kont/' prefix as I showed above allows to keep the URI space totally self-contained in the sitemap. I'd be happy to hear more comments in this area (Daniel?) More ways to get flowmap data ----------------------------- Currently, in order to have access to flowmap data (parameters or continuations), you need to use the XSP or write java code yourself (I'm not even sure the latest is possible, Ovidiu?) I'd like to have at least a few others: 1) XSTL 2) Velocity 3) JSP? I don't know how hard it is to implement them and I'd like to have suggestions on how to implement at least the first one (the problem with velocity is that its output must be parsed, unlike XSP, so Velocity templates are inherently slower than a compiled XSP, but at least they are easier to understand and to use for many, expecially HTML designers). Using JSP as an MVC view makes sense in those cases where a tool is required for HTML designers to connect to the parameters passed to the JSP. I personally don't care but others might. - oooo - Ok, almost there. There is only one big thing missing: when I thought originally at the flowmap concept, I wanted it to be interchangeable with the sitemap, now I've changed my mind and I think it makes sense to have a sitemap always up front, no matter how 'procedural' your web application is (might even have a single URI for the whole thing and handle everything inside the flowmap) At the same time, when I was explaining the flowmap concept to the guys at SwissRisk, we came up with an idea [gosh, forgot the name of the guy who suggested me to think in that direction, hope he is subscribed and makes himself known!]. Consider this flowmap call sendPage("hello.html"); this means: 1) ask the sitemap to redirect to "hello.html" 2) send the output to the client 3) wait for a request to make me continue Now, suppose we do this callPipeline("hello.html", input, output); where we mean: 1) call the internal "hello.html" URI connecting the input and output that I give you. 2) come back here without sending anything to the client <drum-roll/> VOILA'! We have the ability to use serializers to write on disk, without even touching the sitemap! So, consider this sitemap: <map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0"> <map:flowmaps default="editor"> <map:flowmap name="editor" src="editor.js" language="javascript"/> </map:flowmaps> <map:pipelines> <map:pipeline internal="true"> <map:match pattern="save2disk"> <map:generate type="stream"> <map:parameter name="content" value="content"/> </map:generate> <map:serialize type="xml"/> </map:match> </map:pipeline> <map:pipeline> <map:match pattern="save"> <map:call function="save2disk()"/> </map:match> </map:pipeline> </map:pipelines> </map:sitemap> and your flowmap is something like function save2disk() { OutputStream output = new FileOutputStream("/usr/local/docs"); callPipeline("save",cocoon.request, output); } it would be *THAT* easy! [NOTE: the 'input' and 'output' parameters of the callPipeline() method will have to be carefully choosen, here I'm just making this up as an example, don't take this as a complete proposal, but just a way to sparkle discussion] - oooo - There are a few important issues with the flowmap: 1) documentation must be provided on the FOM (flowmap object model), sort of a DOM for flowmaps. Currently, objects like 'cocoon', 'log' and others are provided, but there is no documentation presenting this 2) mappings between java and javascript: I don't know how much coupling can be done between java and javascript in the flowmap, this must be documented more. - oooo - Ok, please, send your comments. Ciao. -- Stefano Mazzocchi One must still have chaos in oneself to be able to give birth to a dancing star. <[EMAIL PROTECTED]> Friedrich Nietzsche -------------------------------------------------------------------- --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, email: [EMAIL PROTECTED]