Reinhard Poetz wrote:

Daniel Fagerstrom wrote:

Reinhard Poetz wrote:

<snip/>

What does extends actually buy us here, couldn't we just use "requrires" for the relation to portal-skin/1.0.0.

IIUC using "extends" was the original idea here.

I know, but I don't see any reasons to take that as written in stone. Extends is an implementation aspect in the otherwise declarative block.xml and as we have discussed it is probably better to be more explicit in the sitemap about what and how a block exposes things. Thurthermore since Stefano gave his original design there have been a trend towards considering the sitemap as the central point for configuration, look at Sylvain's and Carsten's recent work e.g. So my question remains, what does "extends" buy us?


<requires block="http://cocoon.apache.org/blocks/portal-skin/1.0.0"; name="skin"/>

and be explicit about what we expose from portal-skin/1.0.0?

SITEMAP.XMAP
<map:match pattern="one-special.css">
  <map:read src="styles/css/one-special.css"/>
</map:match>



Being explicit means that we end the sitemap with:

<map:mount uri-prefix="" src="blocks://skin"/>

>

                                          - o -

The project that wants to use the Portal is in the block "MyPortal". It needs several components from "Portal" (generator, several transformers) and it needs a block that provides the skin, or more precisly, it needs a block that implements http://cocoon.apache.org/interfaces/portal-skin/1.0. This can either be "portal-skin", the default skin, or "MySkin", that provides one additional CSS. Everything else is taken from "portal-skin".


From my POV this solution is very clear and comprehensible. The aspects "portal functionality" and "skinning" are separated and the used implementations can simply be replaced by other implementations (shown by using the "mySkin" block).


                                          - o -

What does your solution that ueses multiple inheritance look like? (If this is a bad example to show the advantages of MI feel free to enhance it!)



First I will not use the term MI as it doesn't describe what I want to achieve that well and as it also seem to stir all kinds of bad reactions that distracts us from the task at hand.


So I agree with most of what you show in your example, it looks neat. What I lack from it is how to reuse the sitemap in the Portal block.

I would have a sitemap similar to the one in the demo portal in the Portal block. But e.g. the profiles part in the portal-handler configuration would rather be:

Portal Sitemap
--------------

...
<profiles>
<copletbasedata-load uri="blocks:/load-global-profile?profile=copletbasedata"/>
<copletdata-global-load uri="blocks:/load-global-profile?profile=copletdata"/>
<copletdata-role-load uri="blocks:/load-role-profile?profile=copletdata"/>
<copletdata-user-load uri="blocks:/load-user-profile?profile=copletdata"/>
<copletinstancedata-global-load uri="blocks:/load-global-profile?profile=copletinstancedata"/>
<copletinstancedata-role-load uri="blocks:/load-role-profile?profile=copletinstancedata"/>
<copletinstancedata-user-load uri="blocks:/load-user-profile?profile=copletinstancedata"/>
<copletinstancedata-user-save uri="blocks:/save-user-profile?profile=copletinstancedata"/>
<layout-global-load uri="blocks:/load-global-profile?profile=layout"/>
<layout-role-load uri="blocks:/load-role-profile?profile=layout"/>
<layout-user-load uri="blocks:/load-user-profile?profile=layout"/>
<layout-user-save uri="blocks:/save-user-profile?profile=layout"/>
</profiles>
...


Meaning that the different configuration pipelines are found through the blocks manager that would ask the extending block (recursively) for the configuration pipelines first, and if they not are found there, the own pipeline would be used.

Then MyPortal could redefine some of the configuration pipelines and reuse the rest from Portal:

MyPortal Sitemap
----------------

...
<pipeline>
 <match pattern="load-user-profile">
   ...
 </match>

 <mount uri-prefix="" src="blocks://portal"/>
</pipline>
...

Now this mechanism is more limited than real inheritance. map:mount become a two way contract where the mounting sitemap can be asked about services through the block manager, but it doesn't export the interface of the "extended" block. If we have something like the we probably should have some way to differ between mounts that allow the mounted block to ask and those who don't.

(I think) I understand what you want. You called it an "application block" which is the base for all your applications. This application block (e.g. a company portal) provides services that can be used and customized by other blocks.

No it's much simpler ;) I'm not talking about reuse I'm talking about *use*. To make blocks a success they must of course be easy to use in user applications. And to make shure that they are easy to use, we must of course discuss what it looks like when we really use them.


So my application block above is not at all intended for reuse, it is an actual application (actually nearly all of what I have discussed in this thread is about ease of use rather than reuse). If you don't like packaging your applications as blocks that's fine with me we can discuss that case instead.

The reason that I want to package my applications as (not intended for reuse) blocks is that it will solve a number of practical problems for us in a convenient way. Today I have a (large) number of applications running under the same Cocoon at my developer machine. When we have added some new functionality to a webapp I run a script that packages the particular webapp with dependencies and some deployment info and installs it on a production machine. When the customer is satisfied with the test release I update the production release in a similar way. Packaging my webapp as a block will make it able for me to achieve the same thing without much scripting at all as it takes care about dependencies, deploy time parameters (db connections e.g.) and packaging.

For the idea of creating a reusable "application block" it is in my experience not such a good idea. We started that way when we wanted to reuse a whole application, using the design pattern that I have tried to explain in this thread. We did it more because of time constraint than because we actually believed it to be a good idea. Since then we have factored out a numeber of "vertical" blocks from the original application that handles different concerns, and that makes reuse much more flexible.


                                 --- o0o ---

Concerning the skin I find it somewhat burocratic to need to define a new block for beeing able to extend it but I'm ok with it for the time beeing, we will see when we start to use the things. What I would prefer would be to do something like:

MyPortal Sitemap
----------------

...
<pipeline>
 <match pattern="load-user-profile">
   ...
 </match>

 <match pattern="skin/one-special.css">
   <read src="styles/css/one-special.css"/>
  </match>

 <mount uri-prefix="skin" src="blocks://skin"/>

 <mount uri-prefix="" src="blocks://portal"/>
</pipline>
...

                                 --- o0o ---

So what do you think about this?


I think I got the idea. Personally, I would solve this by composition; the profiling is just another reference of the block. Of course, as Stefano said, blocks have to be designed for this. If the application block hasn't factored out the profile aspect, then you can't replace implementation A by implementation B:

---------------------------------------------------------------------------

"Application block" (to be reused in many projects)
---------------------------------------------------------------------------


<block xmlns="http://apache.org/cocoon/blocks/cob/1.0";
 id="http://mycompany.com/blocks/application-block/1.0.0";>
  <name>Application block</name>
  <requirements>
    <requires interface="http://mycompany.com/interfaces/skin/1.0";
      name="skin"
      default="http://mycompany.com/blocks/myskin/1.0.0"/>
    <requires interface="http://cocoon.apache.org/interfaces/profile/1.0";
      name="profile"
      default="http://cocoon.apache.org/blocks/portal/1.0.0"/>
    <requires interface="http://cocoon.apache.org/interfaces/portal/1.0";
      name="portal"
      default="http://cocoon.apache.org/blocks/portal/1.0.0"/>
  </requirements>
</block>

It would work, but to achieve what I did in a few sitemap lines (above) you have to create and deploy a special MyProfile block, and as I just wanted to override a few things in my customized profile I have to extend the original profile from the Portal block in some way. In the sitemap in MyProfile you have to write a copy of the profiles section in the Portal block:


MyProfile sitemap
-----------------

...
<profiles>
<copletbasedata-load uri="blocks:/profile/load-global-profile?profile=copletbasedata"/>
<copletdata-global-load uri="blocks:/profile/load-global-profile?profile=copletdata"/>
<copletdata-role-load uri="blocks:/profile/load-role-profile?profile=copletdata"/>
<copletdata-user-load uri="blocks:/profile/load-user-profile?profile=copletdata"/>
<copletinstancedata-global-load uri="blocks:/profile/load-global-profile?profile=copletinstancedata"/>
<copletinstancedata-role-load uri="blocks:/profile/load-role-profile?profile=copletinstancedata"/>
<copletinstancedata-user-load uri="blocks:/profile/load-user-profile?profile=copletinstancedata"/>
<copletinstancedata-user-save uri="blocks:/profile/save-user-profile?profile=copletinstancedata"/>
<layout-global-load uri="blocks:/profile/load-global-profile?profile=layout"/>
<layout-role-load uri="blocks:/profile/load-role-profile?profile=layout"/>
<layout-user-load uri="blocks:/profile/load-user-profile?profile=layout"/>
<layout-user-save uri="blocks:/profile/save-user-profile?profile=layout"/>
</profiles>
...
<pipeline>
<match pattern="profile/load-user-profile">
...
</match>


Here the blocks protocol is supposed to first look in the own sitemap (and find "load-user-profile") before it asks the super block. As you can see you need a copy of the profiles section of the sitemap booth in Portal and in MyProfile. And you define and deploy small blocks for each aspect you want to override in the original block. The behaviour of an actual application will be spread out in all these small extended blocks. I found it clumsy, complicated and burocratic compared to the simple sitemap based extension mechanism I used in my example.

What I do like is <mount uri-prefix="" src="blocks://portal"/> which makes it explicit what a block exports. I'm not sure about why this has to be a "two way contract".

It must be a two way contract, as "extend and overide" is a two way contract. If you have it one way you end up having to copy everything that is an agregate of functionality as in the profile example above.


For another example of two way communication beween a default sitemap and an extending one you can look at Forrest which make good use of allowing user extensions (http://forrest.apache.org/docs/dev/sitemap-ref.html, http://svn.apache.org/viewcvs.cgi/forrest/trunk/main/webapp/sitemap.xmap?view=markup e.g.).

Here is an example:

   <map:resource name="skinit">
       <map:select type="exists">
         <map:when 
test="{project:skins-dir}{forrest:skin}/xslt/html/{type}.xsl">
           <map:transform 
src="{project:skins-dir}{forrest:skin}/xslt/html/{type}.xsl">
             <map:parameter name="notoc" value="{notoc}"/>
             <!-- For backwards-compat with 0.2 - 0.4 skins -->
             <map:parameter name="isfaq" value="{notoc}"/>
             <map:parameter name="nopdf" value="{nopdf}"/>
             <map:parameter name="path" value="{path}"/>
             <map:parameter name="config-file" value="{project:skinconf}"/>
           </map:transform>
         </map:when>
         <map:otherwise>
           <map:transform 
src="{forrest:context}/skins/{forrest:skin}/xslt/html/{type}.xsl">
             <map:parameter name="notoc" value="{notoc}"/>
             <!-- For backwards-compat with 0.2 - 0.4 skins -->
             <map:parameter name="isfaq" value="{notoc}"/>
             <map:parameter name="nopdf" value="{nopdf}"/>
             <map:parameter name="path" value="{path}"/>
             <map:parameter name="config-file" value="{project:skinconf}"/>
           </map:transform>
         </map:otherwise>
       </map:select>
     <map:serialize/>
   </map:resource>

if the style sheet exists in the extending directory given by "project:skins-dir" it is used otherwise the default variant from "forrest:context" is used. As you see it is a two way communication there Forrest need explicit info about the loocation of the extending application at deploy time. With the mechanism I propose we would get:

Forrest block sitemap
---------------------

   <map:resource name="skinit">
     <map:transform src="blocks:/skins/{forrest:skin}/xslt/html/{type}.xsl">
     <map:parameter name="notoc" value="{notoc}"/>
     <!-- For backwards-compat with 0.2 - 0.4 skins -->
     <map:parameter name="isfaq" value="{notoc}"/>
     <map:parameter name="nopdf" value="{nopdf}"/>
     <map:parameter name="path" value="{path}"/>
     <map:parameter name="config-file" value="{project:skinconf}"/>
     </map:transform>
     <map:serialize/>
   </map:resource>

instead and could use Forrest from our own app with:

Own documentation pipeline
--------------------------

<pipeline>
 <match pattern="skins/common/xslt/html/document2html.xsl">
   ...
 </match>

 <mount uri-prefix="" src="blocks://forrest"/>
</pipeline>

where mount:/ gives polymorphic lookup. The advantage, except for being terser, with this solution compared to the one Forrest use today for the block context is that the Forrest block doesn't need to know any explicit paths to the extending application wich is especially important if you want to use the same block in several contexts.

If you write <copletdata-role-load uri="blocks:/load-role-profile?profile=copletdata"/> then it means that you make it explicit what can be overriden and what not.
I would use <copletdata-role-load uri="blocks:/profile/load-role-profile?profile=copletdata"/> which requires another reference. Using the proposed <mount>-mechanism, you can reuse the "portal"-pipeline.

I'm not certain that I follow you. I would (possibly) use:

 blocks://profile/load-profile

for refering to another block, and:

 blocks:/load-profile

for refering to the same block possibly with sub protocols:

 blocks:polymorph:

if we want to be explicit about when something can be looked up from an extending sitemap

 blocks:super:

for refereing to the extended block.

A problem with blocks://profile, rather than blocks:profile// is if one want to access the "root" block sitemap from a sub sitemap, but that could maybe be done with blocks://this/.

                                       - o -

I think for now we shoudn't support these two-way contracts but favour composition by references (incl. <map:mount uri="[another-block]"/>). If this gets too bureaucratic, we can still think about alternatives.

WDYT?

I think that it becomes to bureaucratic. But you maybe have some better solutions to the issues I adress above. Also, I'm intersted in your view about how to actually use the blocks in an application.


/Daniel



Reply via email to