You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2006/11/24 21:16:19 UTC
svn commit: r478977 - in /tapestry/tapestry5/tapestry-core/trunk/src:
main/java/org/apache/tapestry/corelib/components/
main/java/org/apache/tapestry/internal/services/
main/java/org/apache/tapestry/runtime/ site/apt/guide/ test/conf/
test/java/org/apa...
Author: hlship
Date: Fri Nov 24 12:16:18 2006
New Revision: 478977
URL: http://svn.apache.org/viewvc?view=rev&rev=478977
Log:
Allow direct access to the raw event context from component event handler methods.
Change the Form component to emit more notification events.
Modified:
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/Form.java
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentEventImpl.java
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/LinkFactoryListener.java
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/OnEventWorker.java
tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/runtime/ComponentEvent.java
tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/coercion.apt
tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/event.apt
tapestry/tapestry5/tapestry-core/trunk/src/test/conf/testng.xml
tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/OnEventWorkerTest.java
Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/Form.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/Form.java?view=diff&rev=478977&r1=478976&r2=478977
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/Form.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/corelib/components/Form.java Fri Nov 24 12:16:18 2006
@@ -17,6 +17,7 @@
import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
+import java.util.List;
import org.apache.tapestry.ComponentAction;
import org.apache.tapestry.ComponentResources;
@@ -31,6 +32,7 @@
import org.apache.tapestry.annotations.Inject;
import org.apache.tapestry.annotations.Mixin;
import org.apache.tapestry.annotations.OnEvent;
+import org.apache.tapestry.annotations.Parameter;
import org.apache.tapestry.annotations.SetupRender;
import org.apache.tapestry.corelib.mixins.RenderInformals;
import org.apache.tapestry.dom.Element;
@@ -45,6 +47,18 @@
/**
* An HTML form, which will enclose other components to render out the various types of fields.
+ * <p>
+ * A Form emits several notification events; when it renders it sends a
+ * {@link #PREPARE_EVENT#prepare} notification event, to allow any listeners to set up the state of
+ * the page prior to rendering out the form's content.
+ * <p>
+ * When the form is submitted, the component emits two events: first another prepare event to allow
+ * the page to update its state as necessary to prepare for the form submission, and then a
+ * {@link #SUBMIT submit} event once all data in the form has been propogated into the data model.
+ * <p>
+ * For all of these notifications, the event context is derived from the <strong>context</strong>
+ * parameter. This context is encoded into the form's action URI (the parameter is not read when the
+ * form is submitted, instead the values encoded into the form are used).
*/
@ComponentClass
public class Form
@@ -59,6 +73,14 @@
public static final String SUBMIT = "submit";
/**
+ * The context for the link (optional parameter). This list of values will be converted into
+ * strings and included in the URI. The strings will be coerced back to whatever their values
+ * are and made available to event handler methods.
+ */
+ @Parameter
+ private List<?> _context;
+
+ /**
* Query parameter name storing form data (the serialized commands needed to process a form
* submission).
*/
@@ -108,10 +130,6 @@
_environment.push(FormSupport.class, support);
- // Now that the environment is setup, inform the component or other listeners that the form is about to
- // render.
-
- _resources.triggerEvent(PREPARE_EVENT, null, null);
}
private Element _div;
@@ -119,9 +137,17 @@
@BeginRender
void begin(MarkupWriter writer)
{
+ // Now that the environment is setup, inform the component or other listeners that the form
+ // is about to
+ // render.
+
+ Object[] contextArray = _context == null ? new Object[0] : _context.toArray();
+
+ _resources.triggerEvent(PREPARE_EVENT, contextArray, null);
+
String name = _pageRenderSupport.allocateClientId(_resources.getId());
- Link link = _resources.createActionLink(TapestryConstants.DEFAULT_EVENT, true);
+ Link link = _resources.createActionLink(TapestryConstants.DEFAULT_EVENT, true, contextArray);
writer.element("form", "name", name, "id", name, "method", "post", "action", link
.toFormURI());
@@ -169,8 +195,10 @@
@SuppressWarnings("unchecked")
@OnEvent("action")
- void onSubmit()
+ void onSubmit(Object[] context)
{
+ _resources.triggerEvent(PREPARE_EVENT, context, null);
+
// TODO: Ajax stuff will eventually mean there are multiple values for this parameter name
String actionsBase64 = _request.getParameter(FORM_DATA);
@@ -200,7 +228,7 @@
// TODO: The return value should be used to control what renders.
- _resources.triggerEvent(SUBMIT, null, null);
+ _resources.triggerEvent(SUBMIT, context, null);
}
}
Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentEventImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentEventImpl.java?view=diff&rev=478977&r1=478976&r2=478977
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentEventImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/ComponentEventImpl.java Fri Nov 24 12:16:18 2006
@@ -12,137 +12,142 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package org.apache.tapestry.internal.services;
-
-import org.apache.tapestry.ComponentEventHandler;
-import org.apache.tapestry.ComponentResources;
+package org.apache.tapestry.internal.services;
+
+import org.apache.tapestry.ComponentEventHandler;
+import org.apache.tapestry.ComponentResources;
import org.apache.tapestry.ioc.internal.util.Defense;
-import org.apache.tapestry.ioc.services.TypeCoercer;
-import org.apache.tapestry.runtime.Component;
-import org.apache.tapestry.runtime.ComponentEvent;
-
-public class ComponentEventImpl implements ComponentEvent
-{
- private final String _eventType;
-
- private final String _originatingComponentId;
-
- private final Object[] _context;
-
- private final ComponentEventHandler _handler;
-
- private final TypeCoercer _typeCoercer;
-
- private final int _contextLength;
-
- private boolean _aborted;
-
- private Component _component;
- private String _methodDescription;
-
- /**
- * @param eventType
- * non blank string used to identify the type of event that was triggered
- * @param originatingComponentId
- * the id of the component that triggered the event (this will likely need to change
- * somewhat)
- * @param context
- * an array of values that can be made available to handler methods via method
- * parameters
- * @param handler
- * invoked when a non-null return value is obtained from an event handler method
- * @param typeCoercer
- * used when coercing context values to parameter types
- */
- public ComponentEventImpl(String eventType, String originatingComponentId, Object[] context,
- ComponentEventHandler handler, TypeCoercer typeCoercer)
- {
- _eventType = Defense.notBlank(eventType, "eventType");
- _originatingComponentId = originatingComponentId;
- _context = context;
- _handler = Defense.notNull(handler, "handler");
- _typeCoercer = Defense.notNull(typeCoercer, "typeCoercer");
-
- _contextLength = context != null ? context.length : 0;
- }
-
- public boolean isAborted()
- {
- return _aborted;
- }
-
- /**
- * TODO: This implementation is broken, but will get the job done for simple cases. It just
- * doesn't do quite the right think when an event bubbles up past its originating component's
- * container (can lead to false matches).
- */
- public boolean matchesByComponentId(ComponentResources resources, String[] componentId)
- {
- for (String id : componentId)
- {
- if (id.equals(_originatingComponentId))
- return true;
- }
-
- return false;
- }
-
- public boolean matchesByEventType(String[] eventTypes)
- {
- for (String type : eventTypes)
- {
- if (type.equals(_eventType))
- return true;
- }
-
- return false;
- }
-
- public void setSource(Component component, String methodDescription)
- {
- _component = component;
- _methodDescription = methodDescription;
- }
-
- public boolean storeResult(Object result)
- { // Given that this method is *only* invoked from code
- // that is generated at runtime and proven to be correct,
- // this should never, ever happen. But what the hell,
- // let's check anyway.
-
- if (_aborted)
- throw new IllegalStateException(ServicesMessages
- .componentEventIsAborted(_methodDescription));
-
- if (result == null)
- return false;
-
- _handler.handleResult(result, _component, _methodDescription);
-
- _aborted = true;
-
- return true;
- }
-
- @SuppressWarnings("unchecked")
- public Object coerceContext(int index, String desiredTypeName)
- {
- if (index >= _contextLength)
- throw new IllegalArgumentException(ServicesMessages
- .contextIndexOutOfRange(_methodDescription));
-
- try
- {
- Class desiredType = Class.forName(desiredTypeName);
-
- return _typeCoercer.coerce(_context[index], desiredType);
- }
- catch (Exception ex)
- {
- throw new IllegalArgumentException(ServicesMessages.exceptionInMethodParameter(
- _methodDescription,
- index,
- ex), ex);
- }
- }
-}
+import org.apache.tapestry.ioc.services.TypeCoercer;
+import org.apache.tapestry.runtime.Component;
+import org.apache.tapestry.runtime.ComponentEvent;
+
+public class ComponentEventImpl implements ComponentEvent
+{
+ private final String _eventType;
+
+ private final String _originatingComponentId;
+
+ private final Object[] _context;
+
+ private final ComponentEventHandler _handler;
+
+ private final TypeCoercer _typeCoercer;
+
+ private boolean _aborted;
+
+ private Component _component;
+
+ private String _methodDescription;
+
+ /**
+ * @param eventType
+ * non blank string used to identify the type of event that was triggered
+ * @param originatingComponentId
+ * the id of the component that triggered the event (this will likely need to change
+ * somewhat)
+ * @param context
+ * an array of values that can be made available to handler methods via method
+ * parameters
+ * @param handler
+ * invoked when a non-null return value is obtained from an event handler method
+ * @param typeCoercer
+ * used when coercing context values to parameter types
+ */
+ public ComponentEventImpl(String eventType, String originatingComponentId, Object[] context,
+ ComponentEventHandler handler, TypeCoercer typeCoercer)
+ {
+ _eventType = Defense.notBlank(eventType, "eventType");
+ _originatingComponentId = originatingComponentId;
+ _context = context != null ? context : new Object[0];
+ _handler = Defense.notNull(handler, "handler");
+ _typeCoercer = Defense.notNull(typeCoercer, "typeCoercer");
+
+ }
+
+ public boolean isAborted()
+ {
+ return _aborted;
+ }
+
+ /**
+ * TODO: This implementation is broken, but will get the job done for simple cases. It just
+ * doesn't do quite the right think when an event bubbles up past its originating component's
+ * container (can lead to false matches).
+ */
+ public boolean matchesByComponentId(ComponentResources resources, String[] componentId)
+ {
+ for (String id : componentId)
+ {
+ if (id.equals(_originatingComponentId))
+ return true;
+ }
+
+ return false;
+ }
+
+ public boolean matchesByEventType(String[] eventTypes)
+ {
+ for (String type : eventTypes)
+ {
+ if (type.equals(_eventType))
+ return true;
+ }
+
+ return false;
+ }
+
+ public void setSource(Component component, String methodDescription)
+ {
+ _component = component;
+ _methodDescription = methodDescription;
+ }
+
+ @SuppressWarnings("unchecked")
+ public boolean storeResult(Object result)
+ { // Given that this method is *only* invoked from code
+ // that is generated at runtime and proven to be correct,
+ // this should never, ever happen. But what the hell,
+ // let's check anyway.
+
+ if (_aborted)
+ throw new IllegalStateException(ServicesMessages
+ .componentEventIsAborted(_methodDescription));
+
+ if (result == null)
+ return false;
+
+ _handler.handleResult(result, _component, _methodDescription);
+
+ _aborted = true;
+
+ return true;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Object coerceContext(int index, String desiredTypeName)
+ {
+ if (index >= _context.length)
+ throw new IllegalArgumentException(ServicesMessages
+ .contextIndexOutOfRange(_methodDescription));
+
+ try
+ {
+ Class desiredType = Class.forName(desiredTypeName);
+
+ return _typeCoercer.coerce(_context[index], desiredType);
+ }
+ catch (Exception ex)
+ {
+ throw new IllegalArgumentException(ServicesMessages.exceptionInMethodParameter(
+ _methodDescription,
+ index,
+ ex), ex);
+ }
+ }
+
+ public Object[] getContext()
+ {
+ return _context;
+ }
+
+}
Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/LinkFactoryListener.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/LinkFactoryListener.java?view=diff&rev=478977&r1=478976&r2=478977
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/LinkFactoryListener.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/LinkFactoryListener.java Fri Nov 24 12:16:18 2006
@@ -1,3 +1,17 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package org.apache.tapestry.internal.services;
import org.apache.tapestry.Link;
Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/OnEventWorker.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/OnEventWorker.java?view=diff&rev=478977&r1=478976&r2=478977
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/OnEventWorker.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/services/OnEventWorker.java Fri Nov 24 12:16:18 2006
@@ -12,145 +12,158 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package org.apache.tapestry.internal.services;
-
-import java.util.List;
-
-import org.apache.tapestry.annotations.OnEvent;
+package org.apache.tapestry.internal.services;
+
+import java.util.List;
+
+import org.apache.tapestry.annotations.OnEvent;
import org.apache.tapestry.ioc.util.BodyBuilder;
-import org.apache.tapestry.model.MutableComponentModel;
-import org.apache.tapestry.runtime.Component;
-import org.apache.tapestry.services.ClassTransformation;
-import org.apache.tapestry.services.ComponentClassTransformWorker;
-import org.apache.tapestry.services.MethodSignature;
-import org.apache.tapestry.services.TransformConstants;
-import org.apache.tapestry.services.TransformUtils;
-
-/**
- * Provides implementations of the
- * {@link Component#handleComponentEvent(org.apache.tapestry.runtime.ComponentEvent)} method, based
- * on {@link OnEvent} annotations.
- */
-public class OnEventWorker implements ComponentClassTransformWorker
-{
- public void transform(ClassTransformation transformation, MutableComponentModel model)
- {
- List<MethodSignature> methods = transformation.findMethodsWithAnnotation(OnEvent.class);
-
- // No methods, no work.
-
- if (methods.isEmpty())
- return;
-
- BodyBuilder builder = new BodyBuilder();
- builder.begin();
-
- builder.addln("if ($1.isAborted()) return $_;");
-
- for (MethodSignature method : methods)
- addCodeForMethod(builder, method, transformation);
-
- builder.end();
-
- transformation.extendMethod(TransformConstants.HANDLE_COMPONENT_EVENT, builder.toString());
- }
-
- private void addCodeForMethod(BodyBuilder builder, MethodSignature method,
- ClassTransformation transformation)
- {
- // $1 is the event
-
- int closeCount = 0;
-
- OnEvent annotation = transformation.getMethodAnnotation(method, OnEvent.class);
-
- String[] eventTypes = annotation.value();
-
- if (eventTypes.length > 0)
- {
- String fieldName = transformation.addInjectedField(
- String[].class,
- "eventTypes",
- eventTypes);
-
- builder.addln("if ($1.matchesByEventType(%s))", fieldName);
- builder.begin();
-
- closeCount++;
- }
-
- String[] componentIds = annotation.component();
-
- if (componentIds.length > 0)
- {
- String fieldName = transformation.addInjectedField(
- String[].class,
- "componentIds",
- componentIds);
-
- builder.addln("if ($1.matchesByComponentId(%s, %s))", transformation
- .getResourcesFieldName(), fieldName);
- builder.begin();
-
- closeCount++;
- }
-
- // Ensure that we return true, because *some* event handler method was invoked,
- // even if it chose not to abort the event.
-
- builder.addln("$_ = true;");
-
- // Several subsequent calls need to know the method name.
-
- builder.addln("$1.setSource(this, \"%s.%s\");", transformation.getClassName(), method
- .getMediumDescription());
-
- boolean isNonVoid = !method.getReturnType().equals("void");
+import org.apache.tapestry.model.MutableComponentModel;
+import org.apache.tapestry.runtime.Component;
+import org.apache.tapestry.services.ClassTransformation;
+import org.apache.tapestry.services.ComponentClassTransformWorker;
+import org.apache.tapestry.services.MethodSignature;
+import org.apache.tapestry.services.TransformConstants;
+import org.apache.tapestry.services.TransformUtils;
+
+/**
+ * Provides implementations of the
+ * {@link Component#handleComponentEvent(org.apache.tapestry.runtime.ComponentEvent)} method, based
+ * on {@link OnEvent} annotations.
+ */
+public class OnEventWorker implements ComponentClassTransformWorker
+{
+ static final String OBJECT_ARRAY_TYPE = "java.lang.Object[]";
+
+ public void transform(ClassTransformation transformation, MutableComponentModel model)
+ {
+ List<MethodSignature> methods = transformation.findMethodsWithAnnotation(OnEvent.class);
+
+ // No methods, no work.
+
+ if (methods.isEmpty())
+ return;
+
+ BodyBuilder builder = new BodyBuilder();
+ builder.begin();
+
+ builder.addln("if ($1.isAborted()) return $_;");
+
+ for (MethodSignature method : methods)
+ addCodeForMethod(builder, method, transformation);
+
+ builder.end();
+
+ transformation.extendMethod(TransformConstants.HANDLE_COMPONENT_EVENT, builder.toString());
+ }
+
+ private void addCodeForMethod(BodyBuilder builder, MethodSignature method,
+ ClassTransformation transformation)
+ {
+ // $1 is the event
+
+ int closeCount = 0;
+
+ OnEvent annotation = transformation.getMethodAnnotation(method, OnEvent.class);
+
+ String[] eventTypes = annotation.value();
+
+ if (eventTypes.length > 0)
+ {
+ String fieldName = transformation.addInjectedField(
+ String[].class,
+ "eventTypes",
+ eventTypes);
+
+ builder.addln("if ($1.matchesByEventType(%s))", fieldName);
+ builder.begin();
+
+ closeCount++;
+ }
+
+ String[] componentIds = annotation.component();
+
+ if (componentIds.length > 0)
+ {
+ String fieldName = transformation.addInjectedField(
+ String[].class,
+ "componentIds",
+ componentIds);
+
+ builder.addln("if ($1.matchesByComponentId(%s, %s))", transformation
+ .getResourcesFieldName(), fieldName);
+ builder.begin();
+
+ closeCount++;
+ }
+
+ // Ensure that we return true, because *some* event handler method was invoked,
+ // even if it chose not to abort the event.
+
+ builder.addln("$_ = true;");
+
+ // Several subsequent calls need to know the method name.
+
+ builder.addln("$1.setSource(this, \"%s.%s\");", transformation.getClassName(), method
+ .getMediumDescription());
+
+ boolean isNonVoid = !method.getReturnType().equals("void");
// Store the result, converting primitives to wrappers automatically.
-
- if (isNonVoid)
- builder.add("if ($1.storeResult(($w) ");
-
- builder.add("%s(", method.getMethodName());
-
- for (int i = 0; i < method.getParameterTypes().length; i++)
- {
- if (i > 0)
- builder.add(", ");
-
- addCoercedContextAsParameter(builder, i, method.getParameterTypes()[i]);
- }
-
- if (isNonVoid)
- builder.addln("))) return true;");
- else
- builder.addln(");");
-
- for (int i = 0; i < closeCount; i++)
- builder.end();
- }
-
- private void addCoercedContextAsParameter(BodyBuilder builder, int index, String type)
- {
- boolean isPrimitive = TransformUtils.isPrimitive(type);
- String wrapperType = TransformUtils.getWrapperTypeName(type);
-
- // Add a cast to the wrapper type up front
-
- if (isPrimitive)
- builder.add("((%s)", wrapperType);
-
- // The strings for desired type name will likely repeat a bit; it may be
- // worth it to inject them as final fields. Could increase the number
- // of constructor parameters pretty dramatically, however, and will reduce
- // the readability of the output method bodies.
-
- builder.add("$1.coerceContext(%d, \"%s\")", index, wrapperType);
-
- // and invoke a method on the cast value to get back to primitive
- if (isPrimitive)
- builder.add(").%s()", TransformUtils.getUnwrapperMethodName(type));
- }
-
-}
+
+ if (isNonVoid)
+ builder.add("if ($1.storeResult(($w) ");
+
+ builder.add("%s(", method.getMethodName());
+
+ buildMethodParameters(builder, method);
+
+ if (isNonVoid)
+ builder.addln("))) return true;");
+ else
+ builder.addln(");");
+
+ for (int i = 0; i < closeCount; i++)
+ builder.end();
+ }
+
+ private void buildMethodParameters(BodyBuilder builder, MethodSignature method)
+ {
+ int contextIndex = 0;
+
+ for (int i = 0; i < method.getParameterTypes().length; i++)
+ {
+ if (i > 0)
+ builder.add(", ");
+
+ String type = method.getParameterTypes()[i];
+
+ // Type Object[] is a special case, it gets all of the context parameters in one go.
+
+ if (type.equals(OBJECT_ARRAY_TYPE))
+ {
+ builder.add("$1.getContext()");
+ continue;
+ }
+
+ boolean isPrimitive = TransformUtils.isPrimitive(type);
+ String wrapperType = TransformUtils.getWrapperTypeName(type);
+
+ // Add a cast to the wrapper type up front
+
+ if (isPrimitive)
+ builder.add("((%s)", wrapperType);
+
+ // The strings for desired type name will likely repeat a bit; it may be
+ // worth it to inject them as final fields. Could increase the number
+ // of constructor parameters pretty dramatically, however, and will reduce
+ // the readability of the output method bodies.
+
+ builder.add("$1.coerceContext(%d, \"%s\")", contextIndex++, wrapperType);
+
+ // and invoke a method on the cast value to get back to primitive
+ if (isPrimitive)
+ builder.add(").%s()", TransformUtils.getUnwrapperMethodName(type));
+ }
+ }
+}
Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/runtime/ComponentEvent.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/runtime/ComponentEvent.java?view=diff&rev=478977&r1=478976&r2=478977
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/runtime/ComponentEvent.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/runtime/ComponentEvent.java Fri Nov 24 12:16:18 2006
@@ -12,77 +12,80 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package org.apache.tapestry.runtime;
-
-import org.apache.tapestry.ComponentEventHandler;
-import org.apache.tapestry.ComponentResources;
-import org.apache.tapestry.ComponentResourcesCommon;
-
-/**
- * An event that may originate in application logic, or as a result of a client interaction (a GET
- * or POST from the client).
- *
- * @see ComponentResourcesCommon#triggerEvent(String, Object[],
- * org.apache.tapestry.ComponentEventHandler)
- * @see ComponentEventHandler
- */
-public interface ComponentEvent
-{
- /**
- * Returns true if the event has been aborted (meaning that the return value from some event
- * handler method was accepted, and processing of the event was terminated).
- *
- * @return true if no further event handler methods should be invoked
- */
- boolean isAborted();
-
- /**
- * Returns true if the component event's type matches
- *
- * @param eventTypes
- * @return
- */
- boolean matchesByEventType(String[] eventTypes);
-
- /**
- * Returns true if the originating component matches any of the components identified by their
- * ids. This filter is only relevent in the immediate container of the originating component (it
- * will never match at higher levels).
- */
- boolean matchesByComponentId(ComponentResources resources, String[] componentId);
-
- /**
- * Invoked before {@link #coerceContext(int, String, String)} or
- * {@link #storeResult(Object, String)} to identify, to the event, what method is being acted
- * upon (used for some kinds of exception reporting).
- *
- * @param component
- * the component instance from which the result was obtained
- * @param methodDescription
- */
- void setSource(Component component, String methodDescription);
-
- /**
- * Stores a result for the event. Storing a non-null result value will abort the event.
- *
- * @param result
- * the result obtained from a method invocations
- * @return true if the event is now aborted
- */
- boolean storeResult(Object result);
-
- /**
- * Coerces a context value to a particular type. The context is an array of objects; typically
- * it is an array of strings of extra path information encoded into the action URL.
- *
- * @param <T>
- * @param index
- * the index of the context value
- * @param desiredTypeName
- * the desired type
- * @param methodDescription
- * the method for which the conversion will take place (used if reporting an error)
- * @return the coerced value (a wrapper type if the desired type is a primitive)
- */
- Object coerceContext(int index, String desiredTypeName);
+package org.apache.tapestry.runtime;
+
+import org.apache.tapestry.ComponentEventHandler;
+import org.apache.tapestry.ComponentResources;
+import org.apache.tapestry.ComponentResourcesCommon;
+
+/**
+ * An event that may originate in application logic, or as a result of a client interaction (a GET
+ * or POST from the client).
+ *
+ * @see ComponentResourcesCommon#triggerEvent(String, Object[],
+ * org.apache.tapestry.ComponentEventHandler)
+ * @see ComponentEventHandler
+ */
+public interface ComponentEvent
+{
+ /**
+ * Returns true if the event has been aborted (meaning that the return value from some event
+ * handler method was accepted, and processing of the event was terminated).
+ *
+ * @return true if no further event handler methods should be invoked
+ */
+ boolean isAborted();
+
+ /**
+ * Returns true if the component event's type matches
+ *
+ * @param eventTypes
+ * @return
+ */
+ boolean matchesByEventType(String[] eventTypes);
+
+ /**
+ * Returns true if the originating component matches any of the components identified by their
+ * ids. This filter is only relevent in the immediate container of the originating component (it
+ * will never match at higher levels).
+ */
+ boolean matchesByComponentId(ComponentResources resources, String[] componentId);
+
+ /**
+ * Invoked before {@link #coerceContext(int, String, String)} or
+ * {@link #storeResult(Object, String)} to identify, to the event, what method is being acted
+ * upon (used for some kinds of exception reporting).
+ *
+ * @param component
+ * the component instance from which the result was obtained
+ * @param methodDescription
+ */
+ void setSource(Component component, String methodDescription);
+
+ /**
+ * Stores a result for the event. Storing a non-null result value will abort the event.
+ *
+ * @param result
+ * the result obtained from a method invocations
+ * @return true if the event is now aborted
+ */
+ boolean storeResult(Object result);
+
+ /**
+ * Coerces a context value to a particular type. The context is an array of objects; typically
+ * it is an array of strings of extra path information encoded into the action URL.
+ *
+ * @param <T>
+ * @param index
+ * the index of the context value
+ * @param desiredTypeName
+ * the desired type
+ * @param methodDescription
+ * the method for which the conversion will take place (used if reporting an error)
+ * @return the coerced value (a wrapper type if the desired type is a primitive)
+ */
+ Object coerceContext(int index, String desiredTypeName);
+
+ /** Returns the raw context as a (possibly empty) array. */
+ Object[] getContext();
}
Modified: tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/coercion.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/coercion.apt?view=diff&rev=478977&r1=478976&r2=478977
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/coercion.apt (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/coercion.apt Fri Nov 24 12:16:18 2006
@@ -35,9 +35,10 @@
Merry Christmas: <t:comp type="Count" end="3"> Ho! </t:comp>
+---+
- Here the end parameter is bound to the <literal string> "3".
+ A bare whole number is interpreted by the prop binding prefix as a long. So this is the <long> value 3.
- Tapestry will automatically coerce the bound value, a string, to the parameter's type, int.
+ Tapestry will automatically coerce the bound value, a long, to the parameter's type, int. This may be a lossy coercion (if the long represents a number larger
+ than can be stored in an int).
TypeCoercer Service
Modified: tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/event.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/event.apt?view=diff&rev=478977&r1=478976&r2=478977
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/event.apt (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/site/apt/guide/event.apt Fri Nov 24 12:16:18 2006
@@ -77,6 +77,11 @@
{{{coercion.html}coercion}} from string to the actual type occurs. A runtime exception
occurs if there are more parameters than context strings.
+ Alternately, an event handler method may take a parameter of type java.lang.Object[]. This parameter
+ will receive the entire context array. This is useful when, for example, the context
+ is off different lengths at different times. You should use either explicit, typed parameters or
+ a single parameter of type Object[].
+
Event Bubbling
The event will bubble up the hierarchy, until it is aborted. The event is aborted
@@ -121,5 +126,41 @@
{{{persist.html}page persistence}} to allow information from the action request
to survive to the next render request.
+Passivate and Activate Events
+
+ When a link to a page is created, the page will be sent a passivate event. The event handler method for the
+ passivate event may return a single value, or a list or array of values.
+ These objects will become the context portion of the URI. When a page is later rendered, the context is extracted from the URI and
+ passed to any activate event handlers. In this way, information about the page can be encoded into URIs for the page, in a light-weight
+ (and human readable) manner.
+
+ This was created with the intent of handling pages that exist to view (or perhaps edit) a specific instance of some type; in the
+ context of a CRUD application, the context is used to store the primary key of some entity object:
+
++---+
+
+ @ComponentClass
+ public class ViewCustomer
+ {
+ private Customer _customer;
+
+ @Inject
+ private CustomerDAO _customerDAO;
+
+ @OnEvent("passivate')
+ long passivate()
+ {
+ return _customer.getId();
+ }
+
+ @OnEvent("activate")
+ void activate(long customerId)
+ {
+ _customer = _customerDAO.get(customerId);
+ }
+ }
++---+
+
+ Render page request URI's for this page would be created as <<</ViewCustomer.html/1234>>>.
Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/conf/testng.xml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/conf/testng.xml?view=diff&rev=478977&r1=478976&r2=478977
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/conf/testng.xml (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/conf/testng.xml Fri Nov 24 12:16:18 2006
@@ -15,7 +15,7 @@
limitations under the License.
-->
-<suite name="Tapestry Core" parallel="true" thread-count="10" annotations="1.5" verbose="2">
+<suite name="Tapestry Core" parallel="false" annotations="1.5" verbose="2">
<test name="Tapestry Core">
<packages>
<package name="org.apache.tapestry.integration"/>
Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/OnEventWorkerTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/OnEventWorkerTest.java?view=diff&rev=478977&r1=478976&r2=478977
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/OnEventWorkerTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/services/OnEventWorkerTest.java Fri Nov 24 12:16:18 2006
@@ -14,6 +14,8 @@
package org.apache.tapestry.internal.services;
+import static java.lang.String.format;
+
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.util.Collections;
@@ -388,7 +390,49 @@
new OnEventWorker().transform(ct, model);
verify();
+ }
+
+ @Test
+ public void method_include_context()
+ {
+ ClassTransformation ct = newClassTransformation();
+ MutableComponentModel model = newMutableComponentModel();
+ OnEvent annotation = newOnEvent();
+
+ MethodSignature signature = new MethodSignature(Modifier.PRIVATE, "void", "foo",
+ new String[]
+ { "java.lang.String", OnEventWorker.OBJECT_ARRAY_TYPE, "java.lang.Integer" }, null);
+
+ List<MethodSignature> methods = Collections.singletonList(signature);
+ train_findMethodsWithAnnotation(ct, OnEvent.class, methods);
+
+ train_getMethodAnnotation(ct, signature, OnEvent.class, annotation);
+
+ train_value(annotation, new String[0]);
+ train_component(annotation, new String[0]);
+ train_getClassName(ct, "foo.Bar");
+
+ // Notice that the context doesn't affect the indexing of the other parameters. Though it is
+ // unlikely that a method would use both a context array and explicit context parameters.
+
+ train_extendMethod(
+ ct,
+ TransformConstants.HANDLE_COMPONENT_EVENT,
+ "{",
+ BOILERPLATE_1,
+ BOILERPLATE_2,
+ format("$1.setSource(this, \"foo.Bar.%s\");", signature.getMediumDescription()),
+ "foo($1.coerceContext(0, \"java.lang.String\"), ",
+ "$1.getContext(), ",
+ "$1.coerceContext(1, \"java.lang.Integer\"));",
+ "}");
+
+ replay();
+
+ new OnEventWorker().transform(ct, model);
+
+ verify();
}
protected final OnEvent newOnEvent()