cocoon-cvs mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ghow...@apache.org
Subject svn commit: r165575 - /cocoon/branches/BRANCH_2_1_X/src/documentation/xdocs/tutorial/tutorial-generator.xml
Date Mon, 02 May 2005 02:41:29 GMT
Author: ghoward
Date: Sun May  1 19:41:27 2005
New Revision: 165575

URL: http://svn.apache.org/viewcvs?rev=165575&view=rev
Log:
Fix bug 34283. (Switch from Composeable to Serviceable).
Also added long-missing caching section and 
cleaned up a little.

Modified:
    cocoon/branches/BRANCH_2_1_X/src/documentation/xdocs/tutorial/tutorial-generator.xml

Modified: cocoon/branches/BRANCH_2_1_X/src/documentation/xdocs/tutorial/tutorial-generator.xml
URL: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/documentation/xdocs/tutorial/tutorial-generator.xml?rev=165575&r1=165574&r2=165575&view=diff
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/documentation/xdocs/tutorial/tutorial-generator.xml (original)
+++ cocoon/branches/BRANCH_2_1_X/src/documentation/xdocs/tutorial/tutorial-generator.xml Sun
May  1 19:41:27 2005
@@ -208,24 +208,21 @@
                The only direct implementation of this of interest to us is
                <code>AbstractGenerator</code>, which gives a basic level of
                functionality. Another option would have been
-               <code>ComposerGenerator</code>, which would give us the added
+               <code>ServiceableGenerator</code>, which would give us the added
                functionality of implenting the Avalon interface 
-               <code>Composable</code>
-
-               , which would signal the container that handles all the
-               components including our generator to give us a handle back to
-               the <code>ComponentManager</code>
+               <code>Serviceable</code>, which would signal the container that

+               handles all the components including our generator to give us 
+               a handle back to the <code>ServiceManager</code>
                during the startup of the container. If we needed to lookup a
                pooled database connection, or some other standard or custom
                Cocoon component, this is what we would do. Most of the out
-               of the box Generators extend <code>ComposerGenerator</code>.
-               Other abstract Generators you may choose to extend include the
-               poorly named (IMHO) <code>ServletGenerator</code>
-
-               , and <code>AbstractServerPage</code>
-               . While these both introduce functionality specific to their
-               eventual purpose - the JSP and XSP generators, they do make a
-               convenient starting place for many other Generators.</p>
+               of the box Generators extend <code>ServiceableGenerator</code>.
+               Other abstract Generators you may look to for inspiration 
+               (or choose to extend) include the <code>ServletGenerator</code>,

+               and <code>AbstractServerPage</code>. While these both introduce

+               functionality specific to their eventual purpose - the JSP and 
+               XSP generators, they do make a convenient starting place for many 
+               other Generators.</p>
             </s3>
 
             <s3 title="Running The Sample">
@@ -251,6 +248,9 @@
                    point to <code>lib\core\</code> for them. If you have only
                    the binary version, you can find them in
                    <code>WEB-INF\lib\</code></p>
+                  <note>There are several references to xml-apis.jar in this 
+                  tutorial which should no longer be necessary with JDK 1.4+ 
+                  which ships with the xml apis.</note>
                </s4>
 
                <s4 title="Deploy">
@@ -265,7 +265,9 @@
                   <code>jar</code>-ing them up and place them in
                   <code>WEB-INF\lib\</code> instead. That is probably where
                   your real generators would go anyway - with a whole package
-                  of all your custom classes in one jar.</note>
+                  of all your custom classes in one jar.  UPDATE May 2005: 
+                  I have no idea now what I was talking about when I originally 
+                  wrote this note.</note>
                </s4>
 
                <s4 title="Sitemap Modifications">
@@ -438,14 +440,13 @@
 			<s3 title="Compile and Test">
 			<p>Save this code as
                          <code>RequestExampleGenerator.java</code>
-                         and compile as before.  You will need to add both
-                         <code>avalon-framework.jar</code> and
-                         <code>avalon-excalibur.jar</code> to your classpath
-                        this time.  Besides finding the exact name of the jar
-                        as described above, you may now also have to ensure
-                        that you have the version of excalibur targeted to your
-			jvm version - there is currently a version for JDK 1.4
-                        and one for 1.2/1.3</p>
+                         and compile as before.  You will need to add 
+                         <code>avalon-framework.jar</code>, 
+                         <code>excalibur-pool-api.jar</code>, 
+						<code>excalibur-datasource.jar</code>, and 
+						<code>excalibur-sourceresolve.jar</code> to your classpath
+                        this time.  (Not all of these may be necessary at this point,
+                        but will be later so you might as well add them now.)</p>
 			<p>For your sitemap, you will need to add a definition
                         for this generator like 
 <code><![CDATA[<map:generator name="requestExample" src="RequestExampleGenerator"/>]]></code>
@@ -567,164 +568,166 @@
            this specific task is handled in the ESQL XSP example in just a few
            lines of code.  If your task is really this simple, there may be no
            need to create your own generator.</p>
-<source xml:space="preserve"><![CDATA[import org.apache.cocoon.generation.ComposerGenerator;
-import org.apache.avalon.framework.component.ComponentManager;
+<source xml:space="preserve"><![CDATA[
+import java.io.Serializable;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Date;
+import java.util.Map;
+
+import org.apache.avalon.excalibur.datasource.DataSourceComponent;
+import org.apache.avalon.framework.activity.Disposable;
 import org.apache.avalon.framework.component.ComponentException;
 import org.apache.avalon.framework.component.ComponentSelector;
-import org.apache.avalon.excalibur.datasource.DataSourceComponent;
-import org.apache.cocoon.environment.SourceResolver;
 import org.apache.avalon.framework.parameters.Parameters;
-import org.apache.cocoon.environment.ObjectModelHelper;
-import org.apache.cocoon.environment.Request;
-import org.apache.cocoon.caching.Cacheable;
-import org.apache.cocoon.caching.CacheValidity;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
 import org.apache.cocoon.ProcessingException;
-import org.xml.sax.ContentHandler;
+import org.apache.cocoon.caching.CacheableProcessingComponent;
+import org.apache.cocoon.environment.SourceResolver;
+import org.apache.cocoon.generation.ServiceableGenerator;
+import org.apache.excalibur.source.SourceValidity;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.AttributesImpl;
- 
-import java.sql.*;
-import java.util.Map;
-import java.util.Date;
-import org.apache.avalon.framework.activity.Disposable;
 
-public class EmployeeGeneratorExample extends ComposerGenerator
-   implements Cacheable, Disposable
-{
+public class EmployeeGeneratorExample extends ServiceableGenerator implements
+CacheableProcessingComponent, Disposable {
 
 	public void dispose() {
-	    super.dispose();
-	    manager.release(datasource);
-	    datasource = null;
+		super.dispose();
+		manager.release(datasource);
+		datasource = null;
 	}
 
 	public void recycle() {
-	    myAttr.clear();
-	    super.recycle();
+		myAttr.clear();
+		super.recycle();
 	}
 
-	public void setup(SourceResolver resolver, Map objectModel,
-                          String src, Parameters par) {
-	    // Not neeed for this example, but you would get request
-            // and/or sitemap parameters here.
+	public void setup(SourceResolver resolver, Map objectModel, String src,
+			Parameters par) {
+		// Not neeed for this example, but you would get request
+		// and/or sitemap parameters here.
 	}
 
-	
-	public void compose(ComponentManager manager) 
-	throws ComponentException{
-	  super.compose(manager);
-	  ComponentSelector selector = (ComponentSelector)
-            manager.lookup(DataSourceComponent.ROLE + "Selector");
-	  this.datasource = (DataSourceComponent) selector.select("personnel");
+	public void service(ServiceManager manager) throws ServiceException {
+		super.service(manager);
+		ComponentSelector selector = (ComponentSelector) manager
+				.lookup(DataSourceComponent.ROLE + "Selector");
+		try {
+			this.datasource = (DataSourceComponent) selector
+					.select("personnel");
+		} catch (ComponentException e) {
+			throw new ServiceException("personnel",
+					"Could not find datasource.", e);
+		}
 	}
 
-	public void generate() 
-	throws SAXException, ProcessingException {
+	public void generate() throws SAXException, ProcessingException {
 		try {
 
 			Connection conn = this.datasource.getConnection();
-			Statement stmt = conn.createStatement(); 
-			
+			Statement stmt = conn.createStatement();
+
 			ResultSet res = stmt.executeQuery(EMPLOYEE_QUERY);
-			
-                        //open the SAX event stream
+
+			//open the SAX event stream
 			contentHandler.startDocument();
-			myAttr.addAttribute("","date","date","",
-                            (new Date()).toString());
-                        //open root element
-			contentHandler.startElement("","content",
-                            "content",myAttr);
+			myAttr
+					.addAttribute("", "date", "date", "", (new Date())
+							.toString());
+			//open root element
+			contentHandler.startElement("", "content", "content", myAttr);
 
-			
 			String currentDept = "";
 			boolean isFirstRow = true;
 			boolean moreRowsExist = res.next() ? true : false;
-			
+
 			while (moreRowsExist) {
-			    String thisDept = attrFromDB(res, "name");
-			    if (!thisDept.equals(currentDept)) {
-				newDept(res,thisDept,isFirstRow);
-				currentDept = thisDept;
-			    }
-			    addEmployee(res,attrFromDB(res,"id"),
-                              attrFromDB(res,"empName"));
-			    isFirstRow = false;
-			    
-			    if (!res.next()) {
-				endDept();
-				moreRowsExist = false;
-			    }
+				String thisDept = attrFromDB(res, "name");
+				if (!thisDept.equals(currentDept)) {
+					newDept(res, thisDept, isFirstRow);
+					currentDept = thisDept;
+				}
+				addEmployee(res, attrFromDB(res, "id"), attrFromDB(res,
+						"empName"));
+				isFirstRow = false;
+
+				if (!res.next()) {
+					endDept();
+					moreRowsExist = false;
+				}
 			}
-			
-                        //close root element
-			contentHandler.endElement("","content","content");
-                        //close the SAX event stream
+
+			//close root element
+			contentHandler.endElement("", "content", "content");
+			//close the SAX event stream
 			contentHandler.endDocument();
-			
+
 			res.close();
 			stmt.close();
 			conn.close();
-	  	} catch (SQLException e) { 
-  			throw new ProcessingException(e); 
-  		} 
+		} catch (SQLException e) {
+			throw new ProcessingException(e);
+		}
 	}
 
-	public long generateKey()
-	{
-		// Default non-caching behaviour. We will implement this later.
-		return 0;
+	public Serializable getKey() {
+		// Default non-caching behaviour. We could implement this later.
+		return null;
 	}
 
-	public CacheValidity generateValidity()
-	{
-		// Default non-caching behaviour. We will implement this later.
+	public SourceValidity getValidity() {
+		// Default non-caching behaviour. We could implement this later.
 		return null;
 	}
 
+	private DataSourceComponent datasource;
+
+	private AttributesImpl myAttr = new AttributesImpl();
 
-  private DataSourceComponent datasource;
-  private AttributesImpl myAttr = new AttributesImpl();
-  
-  private String EMPLOYEE_QUERY = 
-  "SELECT department.name, employee.id, employee.name as empName " +
-  "FROM department, employee " + 
-  "WHERE department.id = employee.department_id  ORDER BY department.name";
-  
-  private void endDept() throws SAXException {
-      contentHandler.endElement("","dept","dept");
-  }
-  
-  private void newDept(ResultSet res, String dept, boolean isFirstRow)
-      throws SAXException {
-    if (!isFirstRow) {
-	endDept();
-    }
-    myAttr.clear();
-    myAttr.addAttribute("","name","name","",dept);
-    contentHandler.startElement("","dept","dept",myAttr);
-  }    
-  
-  private void addEmployee(ResultSet res, String id, String name)
-      throws SAXException {
-      myAttr.clear();
-      myAttr.addAttribute("","id","id","",id);
-      contentHandler.startElement("","employee","employee",myAttr);
-      contentHandler.characters(name.toCharArray(),0,name.length());
-      contentHandler.endElement("","employee","employee");
-  }
-  
-  private String attrFromDB(ResultSet res, String column)
-      throws SQLException {
-  		String value = res.getString(column);
-		return (res.wasNull())?"":value;
-  }
+	private String EMPLOYEE_QUERY = "SELECT department.name, employee.id, employee.name as empName
"
+			+ "FROM department, employee "
+			+ "WHERE department.id = employee.department_id  ORDER BY department.name";
+
+	private void endDept() throws SAXException {
+		contentHandler.endElement("", "dept", "dept");
+	}
+
+	private void newDept(ResultSet res, String dept, boolean isFirstRow)
+			throws SAXException {
+		if (!isFirstRow) {
+			endDept();
+		}
+		myAttr.clear();
+		myAttr.addAttribute("", "name", "name", "", dept);
+		contentHandler.startElement("", "dept", "dept", myAttr);
+	}
+
+	private void addEmployee(ResultSet res, String id, String name)
+			throws SAXException {
+		myAttr.clear();
+		myAttr.addAttribute("", "id", "id", "", id);
+		contentHandler.startElement("", "employee", "employee", myAttr);
+		contentHandler.characters(name.toCharArray(), 0, name.length());
+		contentHandler.endElement("", "employee", "employee");
+	}
+
+	private String attrFromDB(ResultSet res, String column) throws SQLException {
+		String value = res.getString(column);
+		return (res.wasNull()) ? "" : value;
+	}
 
 }]]></source></s3>
 	<s3 title="Compile and Test">
 	<p>To compile this, you will now need the following on your classpath:
-         <code>avalon-excalibur.jar, avalon-framework.jar, cocoon.jar,
-         xml-apis.jar</code> (using whatever names they have in your
-         distribution).  When you compile this, you may receive some
+       <code>avalon-framework.jar</code>, 
+       <code>excalibur-pool-api.jar</code>, 
+	   <code>excalibur-datasource.jar</code>, and 
+	   <code>excalibur-sourceresolve.jar</code> (using whatever names they 
+	   have in your distribution).  When you compile this, you may receive some
          deprecation warnings.  Do not worry about them - we will discuss 
          that later.</p>
 	<p>To test it, copy it over to your <code>WEB-INF\classes\</code>
@@ -741,13 +744,13 @@
       </s3>
 
       <s3 title="New Concepts">
-	<s4 title="Composable and Disposable">
-	<p>We've implemented the Avalon lifecycle interfaces Composable and 
+	<s4 title="Serviceable and Disposable">
+	<p>We've implemented the Avalon lifecycle interfaces Serviceable and 
     Disposable.  When Cocoon starts up (which happens when the servlet 
-    container starts up) the <code>ComponentManager</code> will call 
-    <code>compose(ComponentManager m)</code> for our component as it works 
+    container starts up) the <code>ServiceManager</code> will call 
+    <code>service(ServiceManager m)</code> for our component as it works 
     its way through all the components declared in the sitemap.  The handle 
-    to <code>ComponentManager</code> is used to look up any other Avalon 
+    to <code>ServiceManager</code> is used to look up any other Avalon 
     components that we need.  Lookups happen in an abstracted way using a 
     ROLE which enables us to change out implementations of each component 
     without affecting previously written code.  Our generator's ROLE by the 
@@ -757,8 +760,8 @@
     clean up any resources we held on to between invocations.  Note that 
     components can be pooled by the container.  If we thought that our employee 
     generator was going to see a lot of traffic, we might change its definition 
-    at the top of sitemap.xmap to include attributes like <code>pool-max="16"</code>
so that multiple overlapping requests 
-    could be serviced without a log jam.</p>
+    at the top of sitemap.xmap to include attributes like <code>pool-max="16"</code>

+    so that multiple overlapping requests could be serviced without a log jam.</p>
 	</s4>
 	<s4 title="Datasource">
     <p>We look up our HSQL database here by its name given in cocoon.xconf. 
@@ -774,16 +777,72 @@
     support the pooling of statements.</note> 
     </s4>
 	<s4 title="Caching">
-<fixme author="open">Need more content here, or links to other docs.</fixme>
-<note>FIXME: This is still coming.</note>
-    <p>Introduce new code to implement Caching, discuss basic logic, and
-    deprecation/move to Avalon. I could use some help here from Carsten,
-    or someone who can quickly give an overview of the changes and plan.
-    </p>
+    <p>To get started implementing Caching, first read the 
+    <link href="../userdocs/concepts/caching.html">Caching concepts 
+    documentation</link>.  Basically, we would replace our versions of 
+    <code>getKey()</code> and <code>getValidity()</code> to return
a non-null 
+    result.</p>
+    <p>Briefly, the <code>key</code> is any Serializable object that uniquely
identifies 
+    the result within the scope of this component (our Generator).  In our case, 
+    since we are not returning different results based on request parameters, etc. 
+    all results from our Generator will be the same, given the same database state.  
+    We don't need to make this key globally unique, just unique to us.  So, we 
+    could implement getKey() as follows:</p>
+    <source xml:space="preserve"><![CDATA[...
+public Serializable getKey() {
+	return "doesn't matter here";
+}]]></source>
+	<p>The trouble in our example comes when we go to implement <code>getValidity()</code>.
+	This is meant to return an <code>org.apache.excalibur.source.SourceValidity</code>

+	which is responsible for informing the Cocoon pipeline internals whether or not a 
+	cached response found at some point in the future is still valid (should be served) 
+	or not.  In the case of a file system resource this would be easy to determine, for instance.
 
+	In fact, there is a SourceValidity implementation built already for this case (
+	<code>org.apache.excalibur.source.impl.FileTimeStampValidity</code>).
+	</p>
+	<p>In the case of database information, one option would be to devise some system
for tracking 
+	last-modified times on a per-row or per-table basis.  We would still need to query the 
+	database for each request to determine if our cached version is still useful.  Another 
+	option (by far the most common in my experience) would be to decide on some time-period

+	during which we will serve the same result whether or not the database has changed in 
+	the meantime.  To do this, people will usually pick some length of time long enough to 
+	realize benefit from caching, but short enough to minimize the appearance of outdated 
+	data.  There are pre-built options for this as well 
+	(<code>org.apache.excalibur.source.impl.ExpiresValidity</code> for example).
To keep 
+	our results around for five minutes we could implement:
+	</p>
+    <source xml:space="preserve"><![CDATA[...
+public SourceValidity getValidity() {
+    // valid for 600 seconds
+	return new ExpiresValidity(1000*600);
+}]]></source>
+	<p>A third more intruiging option is to utilize some event-based system to 
+	signal that a given result is invalid when the database (or table or row) is actually 
+	updated, and not before.  This has the benefit of greatest cacheability, and least 
+	chance for outdated data, with the disadvantage that it is somewhat complex.  The 
+	"eventcache" block in Cocoon (see also the "jms" block) is intended to provide a 
+	framework to apply this model in Cocoon pipelines.  If your database implements 
+	triggers and stored procedures, and enables interaction with JMS or http calls, 
+	this approach may worth considering.
+	</p>
     </s4>
 	</s3>
-	 </s2>
-      </s1>
+	</s2>
+    </s1>
+    <s1 title="Conclusion">
+    <p>We have covered a lot of ground which should provide a great head start in 
+    implementing not only your own Generators, but any Transformers or Serializers you 
+    may need as well.  Of course, you should also now have an improved insight into some

+    of the workings of the Cocoon core, and its interaction with the Avalon-based framework

+    which would have application for implementing any component whether for interaction 
+    with the sitemap, or more general use in your application.  As is always the case in

+    Open Source Software, you will find the best examples and insight by investigating the

+    existing sources, both those in Cocoon and in its Avalon and Excalibur counterparts 
+    as well.  Armed with this knowledge you should also be able to ask more educated and

+    focused questions on the public mailing lists which you may find will yield better 
+    and faster responses.
+    </p>
+    </s1>
    </body>
 </document>
 



Mime
View raw message