Hi All, Hope all is well.
Sylvain and I have recently had a discussion about the current selector implementation. Here's the first email from the conversation: -------------------------------------------------------------------------------- Hi Sylvain! Hope all is well mate. I remember a while back (>6 months) on cocoon-dev we discussed the semantics of map:select and it's current implementation. I remember you comments were similar to mine, and I'm trying to catch up with where this discussion went (if anywhere), as I've been off in project land for the past few months. My current (perhaps naive) opinion is that map:select is implemented wrong, and causes confusion among new cocoon application developers. The current implementation converts: <map:select type="myselector"> <map:when test="this">...</map:when> <map:when test="that">...</map:when> </map:select> to: if (myselector.select("this"...)) { ... } else if (myselector.select("that"...)) { ... } This seems to be contrary to the semantics the xml implies (and can cause unwanted behaviour) : I think many might expect generated java code to behave like: Object result = myselector.select(...); if (result.equals("this")) { ... } else if (result.equals("that")) { ... } more like a expanded switch statement. ie. the test case is done only once, and its return value is checked multiple times. Not only is this better performance wise, but prevents unwanted behaviour. eg: I found this out when writing a login selector. I had something like: <map:select type="login"> <map:when test="permitted">...</> <map:when test="denied">...</> <map:otherwise>...</> </map:select> which in practice tried to log denied users in 2 times! This means one has to do something like: <map:act type="login"/> <map:select type="userstatus"> <map:when test="permitted">...</> <map:when test="denied">...</> <map:otherwise>...</> </map:select>> </map:act> ie. combination of action and then selector, which I find to be a hack. What do you think ? Has this already been covered on cocoon-dev in recent times ? Cheers, Marcus -------------------------------------------------------------------------------- After some thinking we came up with an idea to create an extended selector interface which supports switch-style semantics rather than if-if-else semantics. The newer interface supports switch-style semantics by allowing a developer to create a 'testable' context before any of the <map:when> tests have been done. This context can then be used in select() to see if the current test yield a true value. The newer interface looks like: public interface ContextualizableSelector extends Selector, ThreadSafe { Object getSelectorContext(Map objectModel, Parameters parameters); boolean select(String expression, Object selectorContext); } There's an Abstract class to support the older style: public abstract class AbstractContextualizableSelector extends AbstractLoggable implements ContextualizableSelector { public boolean select( String expr, Map objectModel, Parameters params ) { return select(expr, getSelectorContext(objectModel, params)); } } A sample implementation would be like: public class RequestParameterSelector extends AbstractContextualizableSelector implements Configurable { public Object getSelectorContext(Map objectModel, Parameters params) { <snip> String name = params.getParameter("parameter-name", this.defaultName); return ObjectModelHelper.getRequest(objectModel).getParameter(name); } public boolean select(String expression, Object selectorContext) { return selectorContext.equals(expression); } } Ok, it's a simple example, but for selectors that calculate more complex conditionals, or do some thing like attempt to log in a user for example, the getSelectorContext(...) would give them a huge saving. The current files use Contextualizable as the prefix which we're not so happy with as a name as it might confuse people with avalon's context or the cocoon environment context. If anyone has a better idea please let us know. So far the only alternative name has been SwitchStyleSelector (?) I've put together some diffs which are attached (updated sitemap.xsl, interfaces and abstract class, and a modified RequestParameterSelector as a sample). We'd be interested in your comments. Cheers, Marcus -- ..... ,,$$$$$$$$$, Marcus Crafter ;$' '$$$$: Computer Systems Engineer $: $$$$: ManageSoft GmbH $ o_)$$$: 82-84 Mainzer Landstrasse ;$, _/\ &&:' 60327 Frankfurt Germany ' /( &&& \_&&&&' &&&&. &&&&&&&:
/* ============================================================================ The Apache Software License, Version 1.1 ============================================================================ Copyright (C) 1999-2002 The Apache Software Foundation. All rights reserved. Redistribution and use in source and binary forms, with or without modifica- tion, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The end-user documentation included with the redistribution, if any, must include the following acknowledgment: "This product includes software developed by the Apache Software Foundation (http://www.apache.org/)." Alternately, this acknowledgment may appear in the software itself, if and wherever such third-party acknowledgments normally appear. 4. The names "Apache Cocoon" and "Apache Software Foundation" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact [EMAIL PROTECTED] 5. Products derived from this software may not be called "Apache", nor may "Apache" appear in their name, without prior written permission of the Apache Software Foundation. THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This software consists of voluntary contributions made by many individuals on behalf of the Apache Software Foundation and was originally created by Stefano Mazzocchi <[EMAIL PROTECTED]>. For more information on the Apache Software Foundation, please see <http://www.apache.org/>. */ package org.apache.cocoon.selection; import org.apache.avalon.framework.component.Component; import org.apache.avalon.framework.parameters.Parameters; import org.apache.avalon.framework.logger.AbstractLoggable; import org.apache.cocoon.selection.Selector; import java.util.Map; /** * Abstract ContextualizableSelector class. * * @author <a href="mailto:[EMAIL PROTECTED]">Marcus Crafter</a> * @author <a href="mailto:[EMAIL PROTECTED]">Sylvain Wallez</a> * @version CVS $Id: Selector.java,v 1.4 2002/02/22 07:03:54 cziegeler Exp $ */ public abstract class AbstractContextualizableSelector extends AbstractLoggable implements ContextualizableSelector { /** * Method to create a selector context. * * @param objectModel The <code>Map</code> containing object of the * calling environment which may be used * to select values to test the expression. * @param parameters The sitemap parameters, as specified by * <parameter/> tags. * @return selector context */ public abstract Object getSelectorContext(Map objectModel, Parameters parameters); /** * Selectors test pattern against some objects in a <code>Map</code> * model and signals success with the returned boolean value * @param expression The expression to test. * @return boolean Signals successful test. */ public abstract boolean select(String expression, Object selectorContext); /** * Selectors test pattern against some objects in a <code>Map</code> * model and signals success with the returned boolean value * @param expr The expression to test. * @return Signals successful test. */ public boolean select(String expr, Map objectModel, Parameters params) { return select(expr, getSelectorContext(objectModel, params)); } }
/* ============================================================================ The Apache Software License, Version 1.1 ============================================================================ Copyright (C) 1999-2002 The Apache Software Foundation. All rights reserved. Redistribution and use in source and binary forms, with or without modifica- tion, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The end-user documentation included with the redistribution, if any, must include the following acknowledgment: "This product includes software developed by the Apache Software Foundation (http://www.apache.org/)." Alternately, this acknowledgment may appear in the software itself, if and wherever such third-party acknowledgments normally appear. 4. The names "Apache Cocoon" and "Apache Software Foundation" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact [EMAIL PROTECTED] 5. Products derived from this software may not be called "Apache", nor may "Apache" appear in their name, without prior written permission of the Apache Software Foundation. THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This software consists of voluntary contributions made by many individuals on behalf of the Apache Software Foundation and was originally created by Stefano Mazzocchi <[EMAIL PROTECTED]>. For more information on the Apache Software Foundation, please see <http://www.apache.org/>. */ package org.apache.cocoon.selection; import org.apache.avalon.framework.component.Component; import org.apache.avalon.framework.parameters.Parameters; import org.apache.avalon.framework.thread.ThreadSafe; import org.apache.cocoon.selection.Selector; import java.util.Map; /** * ContextualizableSelector is an enhanced Selector interface that allows a * context object to be created to optimize selector conditional testing. * * <p> * The original Selector interface supports an <code>if-then-else</code> style * of conditional testing depending on whether a particular expression is true. * This causes Selector.select() to be invoked for each <map:when> * statement which may be undesirable due to performance or logic reasons. * </p> * * <p> * <pre> * Example, the following sitemap snippet: * * <map:select type="aSelector"> * <map:when test="test-expr1">...</map:when> * <map:when test="test-expr2">...</map:when> * </map:select> * * is interpreted as (pseudo-code): * * if (aSelector.select("test-expr1", objectModel, params)) { * ... * } else if (aSelector.select("test-expr2", objectModel, params)) { * ... * } * * ie. aSelector.select(...) is called once for each <map:when> * statement. * </pre> * </p> * * <p> * ContextualizableSelector allows the developer to first create a * context object which is passed with each call to select(). This context * object is created before any conditional tests are made, and hence can be * used to optimize conditional testing. * </p> * * <p> * <pre> * The above example implemented as a ContextualizableSelector would be * interpreted as (psuedo-code): * * Object selectorContext = aSelector.getSelectorContext(objectModel, params); * * if (aSelector.select("test-expr1", selectorContext)) { * ... * else if (aSelector.select("test-expr2", selectorContext)) { * ... * } * * ie. the bulk of the selector's work is done in getSelectorContext(), * select() simply compares whether the expression should be considered true. * </pre> * </p> * * @author <a href="mailto:[EMAIL PROTECTED]">Marcus Crafter</a> * @author <a href="mailto:[EMAIL PROTECTED]">Sylvain Wallez</a> * @version CVS $Id: Selector.java,v 1.4 2002/02/22 07:03:54 cziegeler Exp $ */ public interface ContextualizableSelector extends Selector, ThreadSafe { String ROLE = "org.apache.cocoon.selection.ContextualizableSelector"; /** * Method to create a selector context. * * @param objectModel The <code>Map</code> containing object of the * calling environment which may be used * to select values to test the expression. * @param parameters The sitemap parameters, as specified by * <parameter/> tags. * @return Selector context */ Object getSelectorContext(Map objectModel, Parameters parameters); /** * Contextualizable Selectors test patterns against a context object * and signal success with the returned boolean value * @param expression The expression to test. * @param selectorContext The context this test should be performed in. * @return true if the test was successful. */ boolean select(String expression, Object selectorContext); }
? src/java/org/apache/cocoon/selection/AbstractContextualizableSelector.java ? src/java/org/apache/cocoon/selection/ContextualizableSelector.java Index: src/java/org/apache/cocoon/components/language/markup/sitemap/java/sitemap.xsl =================================================================== RCS file: /home/cvspublic/xml-cocoon2/src/java/org/apache/cocoon/components/language/markup/sitemap/java/sitemap.xsl,v retrieving revision 1.10 diff -u -r1.10 sitemap.xsl --- src/java/org/apache/cocoon/components/language/markup/sitemap/java/sitemap.xsl 1 Mar 2002 15:09:04 -0000 1.10 +++ src/java/org/apache/cocoon/components/language/markup/sitemap/java/sitemap.xsl + 15 Mar 2002 11:36:14 -0000 @@ -193,6 +193,7 @@ import org.apache.cocoon.matching.Matcher; import org.apache.cocoon.matching.PreparableMatcher; import org.apache.cocoon.selection.Selector; + import org.apache.cocoon.selection.ContextualizableSelector; import org.apache.cocoon.sitemap.AbstractSitemap; import org.apache.cocoon.components.pipeline.StreamPipeline; import org.apache.cocoon.components.pipeline.EventPipeline; @@ -242,16 +243,36 @@ /** * Method that handles selectors. */ - private boolean isSelected(String hint, String testValue, Parameters params, Map objectModel) throws Exception { - Selector selector = (Selector)this.selectors.select(hint); + private boolean isSelected(String hint, String testValue, Parameters params, +Map objectModel, Object selectorContext) throws Exception { + Component selector = (Selector)this.selectors.select(hint); try { - return selector.select(testValue, objectModel, params); + if (selector instanceof ContextualizableSelector) + return ((ContextualizableSelector) selector).select( + testValue, selectorContext + ); + else // support normal selector + return ((Selector) selector).select(testValue, objectModel, params); } finally { this.selectors.release(selector); } } /** + * Method that handles selectors. + */ + private Object getSelectorContext(String hint, Map objectModel, Parameters +params) throws Exception { + Component selector = this.selectors.select(hint); + try { + if (selector instanceof ContextualizableSelector) + return ((ContextualizableSelector) +selector).getSelectorContext(objectModel, params); + } finally { + this.selectors.release(selector); + } + // non-ContextualizableSelector + return null; + } + + /** * Method that handles matchers. */ private Map matches(String hint, Object preparedPattern, String pattern, Parameters params, Map objectModel) throws Exception { @@ -959,6 +980,11 @@ </xsl:choose> </xsl:variable> + Object selectorContext = + getSelectorContext( + "<xsl:value-of select="$selector-type"/>", objectModel, <xsl:value-of +select="$component-param"/> + ); + <!-- loop through all the when cases --> <xsl:for-each select="map:when"> <!-- remove all invalid chars from the test expression. The result is used to form the name of the generated method @@ -974,7 +1000,7 @@ <xsl:call-template name="line-number"/> <xsl:if test="position() > 1"> else </xsl:if> - if (isSelected("<xsl:value-of select="$selector-type"/>", <xsl:apply-templates select="@test"/>, <xsl:value-of select="$component-param"/>, objectModel)) { + if (isSelected("<xsl:value-of select="$selector-type"/>", <xsl:apply-templates +select="@test"/>, <xsl:value-of select="$component-param"/>, objectModel, +selectorContext)) { if (debug_enabled) getLogger().debug("Select <xsl:value-of select="$selector-type"/> Test <xsl:value-of select="XSLTFactoryLoader:escape($factory-loader, @test)"/>"); <xsl:apply-templates/> } Index: src/java/org/apache/cocoon/selection/RequestParameterSelector.java =================================================================== RCS file: /home/cvspublic/xml-cocoon2/src/java/org/apache/cocoon/selection/RequestParameterSelector.java,v retrieving revision 1.4 diff -u -r1.4 RequestParameterSelector.java --- src/java/org/apache/cocoon/selection/RequestParameterSelector.java 22 Feb 2002 07:03:54 -0000 1.4 +++ src/java/org/apache/cocoon/selection/RequestParameterSelector.java 15 Mar 2002 +11:36:15 -0000 @@ -53,7 +53,6 @@ import org.apache.avalon.framework.configuration.Configurable; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; -import org.apache.avalon.framework.logger.AbstractLoggable; import org.apache.avalon.framework.parameters.Parameters; import org.apache.avalon.framework.thread.ThreadSafe; @@ -74,10 +73,11 @@ * @author <a href="mailto:[EMAIL PROTECTED]">Christian Haul</a> * @author <a href="mailto:[EMAIL PROTECTED]">Sylvain Wallez</a> * @author <a href="mailto:[EMAIL PROTECTED]">Vadim Gritsenko</a> + * @author <a href="mailto:[EMAIL PROTECTED]">Marcus Crafter</a> * @version CVS $Id: RequestParameterSelector.java,v 1.4 2002/02/22 07:03:54 cziegeler Exp $ */ -public class RequestParameterSelector extends AbstractLoggable - implements Configurable, ThreadSafe, Selector { +public class RequestParameterSelector extends AbstractContextualizableSelector + implements Configurable { protected String defaultName; @@ -85,20 +85,24 @@ this.defaultName = config.getChild("parameter-name").getValue(null); } - public boolean select(String expression, Map objectModel, Parameters parameters) { + public Object getSelectorContext(Map objectModel, Parameters parameters) { + String name = parameters.getParameter("parameter-name", this.defaultName); if (name == null) { getLogger().warn("No parameter name given -- failing."); - return false; + return null; } - String value = ObjectModelHelper.getRequest(objectModel).getParameter(name); - if (value == null) { - getLogger().debug("Request parameter '" + name + "' not set -- failing."); + return ObjectModelHelper.getRequest(objectModel).getParameter(name); + } + + public boolean select(String expression, Object selectorContext) { + if (selectorContext == null) { + getLogger().debug("Request parameter '" + selectorContext + "' not set -- +failing."); return false; } - return value.equals(expression); + return selectorContext.equals(expression); } }
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, email: [EMAIL PROTECTED]