forrest-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Thorsten Scherler <thors...@apache.org>
Subject Re: my trip through the view-related pipelines
Date Thu, 08 Sep 2005 08:03:34 GMT
:)

First of all good to have this fresh wind blowing in my face. ;-)
I really like having you on board.

[NOTE: I snipped the mail apart to fit my explanations and visions of
views]

[NOTE: I assume we are using jx for the forrest:structurer]

************************************************
> Long Version:
> ************************************************
> 
> ***** The Request **** 
> URL request is matched by the following match in viewHelper.xhtml
> plugin. This "kicks
> off" views. 
> 
> 
The situation that we have right now is that:

<map:match pattern="*.html">
> <map:generate src="cocoon://{1}.page"/>
> <map:transform src="cocoon://getStylesheet.xhtml.{1}">
> <map:parameter name="path" value="{0}"/>
> </map:transform>
> <map:transform src="resources/stylesheets/strip_namespaces.xsl"/>
> <map:serialize type="xhtml"/>
> </map:match>
> 
> 
Which should become:
xmap:
<map:match pattern="**.html">
  <map:generate src="{lm:{0}.model.xml}"/>
  <map:transform src="{lm:{0}.viewHelper.xsl}">
  <map:serialize type="xml"/>
<map:match/>

lm:
<match pattern="**.model.xml">
  <location src="cocoon://get.{0}" />
</match>

<match pattern="**.viewHelper.xsl">
  <location src="cocoon://get.{0}" />
</match>

****** The Generator Call Chain ********
> The following is a look at the call chain from the generator in the 
> original request match whose source is cocoon://{1}.page. 
> 
> 
The situation that we have right now is that:

1. The generator pulls gets its source from the following in the
> internal.view plugin:
> 
> <map:match pattern="*.page">
> <map:aggregate element="site">
> <map:part src="cocoon://skinconf.xml"/>
> <map:part src="cocoon://build-info"/>
> <map:part src="cocoon://tab-{1}.html"/>
> <map:part src="cocoon://menu-{1}.html"/>
> <map:part src="cocoon://body-{1}.html"/>
> <map:part src="cocoon:/prepare.view-nugget.{1}"/>
> </map:aggregate>
> <map:serialize type="xml"/>
> </map:match>
> 
> 
As you might expect, this results in a really large page that just
> concatenates the different piece-parts -- some obvious, some maybe
> not.
> 
> 
That is why we have to only request what we need. More then half of the
stuff that you bulled point now is either not need or not used or to
inflexible to override. I will show possible refactoring ideas.

skinconf.xml - colors, familiar stuff
> 
<forrest:contracts/> and <forrest:properties/> is completely replacing
the need for having the skinconf.xml. There are still contracts
dependent on it but they need to be refactored anyway (xhmtl2). We
should use the chance to get rid of skinconf.xml. Half of the stuff is
about placing elements in the old fashion skin the other half is adding
the properties of the outcome (e.g. copyright notice).

build-info - "info" element with version, project-skin, etc.
> 
Should become:
<forrest:contract name="siteinfo-build">
    <forrest:properties contract="siteinfo-build">
      <forrest:property name="siteinfo-build"
nugget="get.build.info">
        <url>build-info</url>
      </forrest:property>
    </forrest:properties>
</forrest:contract>

tab - tabs html chunk
> menu - menu html chunk
> 
Should be united as 

xmap:
<map:match pattern="**.navigation.xml">
  <map:generate src="{lm:navigation.xml}"/>
  <map:transform src="{lm:navigation.xsl}">
   <map:parameter name="request" value="{1}"/>
  </map:transform>
  <map:serialize type="xml"/>
<map:match/>

lm:
<match pattern="*.navigation.xml">
  <location src="site.xml" />
</match>

<match pattern="*.navigation.xsl">
  <location src="site2navigation.xsl" />
</match>

Should be request with e.g. following contract (menu):
<forrest:contract name="nav-section">
    <forrest:properties contract="nav-section">
      <forrest:property name="nav-section"
nugget="get.navigation">
<!--NOTE: JX-->
        <url>${request}.navigation.xml</url>
      </forrest:property>
    </forrest:properties>
</forrest:contract>

body - content wrapped in a div id="content"
> 
Which should become:
xmap:
<map:match pattern=**.source.xml">
  <map:generate src="{lm:{0}}"/>
  <map:serialize type="xml"/>
<map:match/>

lm:
<match pattern="**.source.xml">
  <location src="get.{0}" />
</match>

or (depending whether you want the original source or the xdocs
[internal format])
Which should become:
xmap:
<map:match pattern=**.internal.xml">
  <map:generate src="{lm:{0}}"/>
  <map:serialize type="xml"/>
<map:match/>

lm:
<match pattern="**.internal.xml">
  <location src="get.{0}" />
</match>

prepare-view-nugget.{1} - see next step
> 
> 
Which should be the only thing left in the above match="**.page":
xmap:
<map:match pattern="**.model.xml">
  <map:generate src="{lm:{0}}"/>
  <map:serialize type="xml"/>
<map:match/>

lm:
<match pattern="**.model.xml">
  <location src="get.{0}" />
</match>

Example:
> http://localhost:8888/index.page
> 
> 2. This is just an aggregation of all the content that might be
> useful. Unique to views really the last part,
> "prepare.view-nugget.{1}". 
> 
Yes, this unique part is as well the only thing that should be needed.
The other aggregation parts should be request by contracts on demand.

This resolves to the following in the same
> internal.view plugin.
> 
> <map:match pattern="prepare.view-nugget.**">
> <map:generate src="cocoon:/prepare.view.{1}"/>
> <map:transform src="resources/stylesheets/prepare.view.xsl">
> <map:parameter name="view" value="{1}"/>
> </map:transform>
> <map:transform type="xinclude"/>
> <map:serialize type="xml"/>
> </map:match>
> 
> 

Which should become:
xmap:
<map:match pattern="get.**.model.xml">
  <map:generate src="{lm:dispatcher.{1}}"/>
  <map:transform src="{lm:dispatcher2model.xsl">
    <map:parameter name="view" value="{1}"/>
  </map:transform>
  <map:transform type="xinclude"/> 
  <map:serialize type="xml"/>
<map:match/>


This step and the next are described here because they're so close. 
> The short story is that this step results in a "forrest:views" root
> element to be aggregated in the step above. The interesting part is
> where these come from. Using the prepare.view.** match below, it
> allows these to be defined in multiple places even with different
> extensions. Either way, this is all ultimately to get to a resultant
> xml fragment that looks like default.fv. 

Not exactly. The forrest:structurer is not only a for presentation but
as well for the models. You are right that in the end the
forrest:structurer will look nearly the same but with the big difference
that you will have all your requested models in it. This builds the
final presentation model.

> From a views-user
> perspective it may be interesting to dive into where/how all these can
> be defined
> but for a dev trying to understand the view-pipeline, I think we can
> consider the default result, which is default.fv in the internal.view
> plugin. I've taken a look at prepare.view.xsl in the above but am not
> really sure what it does. Either my XSLT skills are really bad, or
> the transform is really subtle.
> http://localhost:8888/prepare.view-nugget.index.html
> 

 
Actually that is far more then the default.fv (like I tried to explain
above). I imaging you have used the default.fv for testing. In this view
we do not have dynamic includes but that has to change when we want to
use the full potential.

On Tue, 2005-09-06 at 02:33 +0200, Thorsten Scherler wrote:
But a dynamic contract can as well service requests for data from the
> view by simply providing access to the raw data or by formatting the
> data. 
> <forrest:contract name="content-feeder">
>     <forrest:properties contract="content-feeder">
>       <forrest:property name="content-feeder"
> nugget="get.nugget.feeder">
>         <url>feeds/somefeed.xml</url>
>       </forrest:property>
>     </forrest:properties>
> </forrest:contract>

Your example that you used actually were not requesting such external
data. The above example will request rss feed from cocoon://{url}"/>

3. You'll notice that the generator source for this one is the
> following match, again in the
> same internal.view plugin. 
> 
> <map:match pattern="prepare.view.**">
> <map:act type="fallbackResolverAction">
> <map:parameter value="{1}" name="request"/>
> <map:parameter value="{project:theme}" name="projectFallback"/>
> <map:parameter value="{project:theme-ext}" name="projectExtension"/>
> <map:parameter value="{project:content.xdocs}" name="projectDir"/>
> <map:parameter value="{defaults:view-themes}" name="defaultDir"/>
> <map:parameter value="{defaults:theme}" name="defaultFallback"/>
> <map:parameter value="{defaults:theme-ext}"
> name="defaultExtension"/>
> 
> <map:generate src="file:/{uri}"/>
> <map:transform
> src="resources/stylesheets/prepare.include.templates.xsl"/>
> <map:transform type="xinclude"/>
> <map:serialize type="xml"/>
> </map:act>
> </map:match>
> 

That has to be changed like:
xmap:
<map:match pattern="dispatcher.**">
<!--NOTE: JX-->
  <map:generate src="{lm:{0}}" type="jx">
    <map:parameter value="{1}" name="request"/>
  </map:generate>
  <map:serialize type="xml"/>
</map:match>

lm: -> missing the source type action
<match pattern="dispatcher.**">
  <select type="exists">
<!-- File-based - project -->
   <location src="{1}{project:theme-ext}" />
   <act type="RecursiveDirectoryTraversalAction">
    <parameter value="{1}" name="request"/>
    <parameter value="{project:theme}" name="projectFallback"/>
    <parameter value="{project:theme-ext}" name="projectExtension"/>
    <parameter value="{project:content.xdocs}" name="projectDir"/>
<!-- Directory-based / Parent-directory based (recursively) - project
-->
    <location src="{uri}" />
   </act>
<!-- Theme based - default -->
   <location
src="{defaults:view-themes}/{project:theme}{project:theme-ext}" />
<!-- Default theme based - default -->
   <location
src="{defaults:view-themes}/{defaults:theme}{defaults:theme-ext}" />
  </select>
</match>


> It's probably worth noting that when "call-template" is used to call
> an external template, the stylesheet above
> prepare.include.templates.xsl is responsible for implementing this by 
> wapping out "call-template" with "xi-include".

That should be done with jx rather then the stylesheet/xi-include.

> See for yourself with:
> http://localhost:8888/prepare.view.index.html
> 
> 
> Generator Summary: So, this has essentially gone out and brought
> together all of the source data that we might want to use in our
> output to the user, making it accessible to the transform within a
> single xml document.
> 

See my notes above how it should be resolved/dispatched.

> 
> ******* The Transformer Call Chain ********** 
> Now if you go back up to the original request pattern="*.html", let's
> follow the transformer call chain to understand it.
> 
> 1. The source of the transformer in the original request comes
> directly to the following match in the internal.view plugin.
> 
> <map:match pattern="getStylesheet.*.**">
> <map:aggregate element="forrest:filter">
> <map:part src="cocoon://prepare.view.{2}" />
> <map:part src="cocoon://prepare.properties.{1}.{2}" /> 
> </map:aggregate>
> <map:transform src="resources/stylesheets/prepare.{1}.xsl" >
> <map:parameter name="request" value="{2}"/>
> <map:parameter name="forrestContext" value="{forrest:context}"/>
> </map:transform>
> <map:transform type="xinclude"/> 
> <map:serialize type="xml"/>
> </map:match>
> 

This part is heavily build on xsl and alias-xsl. It is quite heavy to
understand and I am still thinking how to re-factor that. I reckon it
should be done with Java rather then xsl but I am unsure whether to
write a generator or a transformer instead. Both have their pros and
cons. Maybe somebody has an idea/advice?

> See for yourself with: 
> http://localhost:8888/getStylesheet.xhtml.index.html 
> 
> We've already seen that the call to prepare.view.{2} will give us an
> equivalent to default.fv, so we won't cover that again. First, go
> read about this prepare.properties{1}.{2} below, then come back and
> read the rest.
> 
> .... 
> 
> So now we have a list of properties about all of the contracts that
> will be used and a view template document roughly equivalent to
> default.fv. Now this, in my mind is where the magic happens. I am
> unable to describe details of *how* the prepare.xhtml.xsl stylesheet
> does it's work but through some pretty clever xsl:namespace-alias, the
> contracts are turned into an "aliased" stylesheet(see bottom). This
> is xsl at it's best I think. Someone smarter than I will have to
> explain the details but this is roughly what I gather in psuedo-codo:
> 
> for each contract in "prepare.properties"
> locate the contract in "prepare.view"
> create an aliased "call-template" appropriate to that contract
> next
> 
> grossly oversimplified? yep, but hopefully you get the idea.
> 

No, perfectly explained. :) The very detail of how I finally got it
working are not so important because this solution has one really big
downside that it depends heavily on the boolean @head and @body. We need
something smarter that we can better reuse in different formats. ;-) 

...but so true, here is where the magic happens.  ;-)

> now lets take an example:
> 
> the following contract shows up in our properties result set:
> <forrest:property name="siteinfo-meta" head="true" body="false"
format="xhtml"/>
> 
> Now in our resultant stylesheet there will be two template calls that
> get most things kicked off, they are: getHead and getBody. The
> looping through the properties let us determine which contracts should
> have an aliased call-template for them. Using our example, the
> "siteinfo-meta" contract would get identified as needing to get called
> in the alias:getHead template. So the following would get created for
> it:
> 
> <alias:template name="getHead">
> <alias:call-template name="siteinfo-meta-head"/>
> </alias:template>
> 
> If there were other contracts that have "head=true" then they would
> also get printed there too based on the loops that reside in getHead. 
> Aww shucks, let's go ahead and take a look at that too:
> 
> <alias:template name="getHead">
> <xsl:for-each 
> select="/*/forrest:properties/*[@head='true' and count(. |
> key('head-template', @name)[1]) = 1]">
> <xsl:variable name="name" select="@name"/>
> <xsl:apply-templates mode="head"
> select="//forrest:contract[@name=$name]"/>
> </xsl:for-each>
> </alias:template>
> 

Isn't that pretty. ;-)

> This ultimately creates the templae above. You see that
> siteinfo-meta-head is the only contract where @head='true' hense, the
> call-template to it.
> 
> 2. We can see that it is aggregating two view related parts to
> become the forrest:filter
> 
...

> 
> <map:match pattern="prepare.properties.*.**">
> <map:generate src="cocoon:/prepare.view.{2}"/>
> <map:transform src="resources/stylesheets/prepare.properties.xsl">
> <!--Which output format?-->
> <map:parameter name="format" value="{1}"/>
> </map:transform>
> <map:transform type="xinclude"/>
> <map:serialize type="xml"/>
> </map:match>
> 
> Interestingly, the second part actually makes a request back to the
> first as it's generator source. It goes and gets the view
> (default.fv equivalent) and includes the properties for each contract
> using another the cocoon match="get.contract-property.*.xhtml". You
> can see the result of that here:
> http://localhost:8888/get.contract-property.siteinfo-meta.xhtml
> 

The problem lies in *how* the get.contract-property.*.xhtml
transformation is picking up the properties from the contract to know
where it has to be placed later on. It is using the attribute of the
contract for it we have to find another way for that.

> So now we have what essentially amounts to a list of properties
> about all of the contracts, now
> go back to where you left off reading above.
> 
> See for yourself here:
> http://localhost:8888/prepare.properties.xhtml.index.html
> 
> Transformer Summary: So the getStylesheet is all about dynamically
> creating an xslt based on contracts and view template. We create a
> list of contracts with their properties (essentially because we need
> to know where they reside in the resultant xslt: head or body). We
> then go through each contract and create a real xslt call-template to
> it (in an alias namespace obviously).
> 
> The use of the alias is tricky at first to those of us who aren't xslt
> experts. Once you think about it it's obvious that if you're using an
> xslt to transform something into an xslt, then the namespace of the
> output xslt would have to be different so as not to confuse whether a
> particular xsl: element belonged to the resultant set or the
> stylesheet doing the transform.

Yes,  if we would not use the alias namespace everything would be
interpreted as transformation instructions and would be applied in the
prepare.html.xsl.

Thanks, Tim, for starting this thread it is very important to understand
what is going on to be able to re-factor views into the core. Still this
is only 1/3 of all involved pipelines and we have a long way to go. ;-)
We should use POJO were ever it make sense to better reuse the code.
Like Ross stated in another mail view pipes have some similarities with
method and functions. I always thought about this implementation as
prototype and think that we should mainly use xsl instead of java where
appropriate. That is the reason for the architecture, that should make
it easy to migrate to java.

salu2
-- 
thorsten

"Together we stand, divided we fall!" 
Hey you (Pink Floyd)


Mime
View raw message