You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by gh...@apache.org on 2005/05/02 04:41:29 UTC
svn commit: r165575 -
/cocoon/branches/BRANCH_2_1_X/src/documentation/xdocs/tutorial/tutorial-generator.xml
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>