You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by th...@apache.org on 2021/09/07 13:31:56 UTC
[tapestry-5] 01/01: TAP5-2692: @StaticActivationContextValue
This is an automated email from the ASF dual-hosted git repository.
thiagohp pushed a commit to branch rest
in repository https://gitbox.apache.org/repos/asf/tapestry-5.git
commit b58a865881495f4494909029614b6d180182eec1
Author: Thiago H. de Paula Figueiredo <th...@arsmachina.com.br>
AuthorDate: Tue Sep 7 10:24:36 2021 -0300
TAP5-2692: @StaticActivationContextValue
---
.../annotations/StaticActivationContextValue.java | 47 +++++++++++++
.../internal/transform/OnEventWorker.java | 60 ++++++++++++++--
.../apache/tapestry5/runtime/ComponentEvent.java | 35 ++++++++++
.../test/app1/StaticActivationContextValueDemo.tml | 27 ++++++++
.../tapestry5/integration/app1/MiscTests.groovy | 16 +++++
.../tapestry5/integration/app1/pages/Index.java | 4 +-
.../pages/StaticActivationContextValueDemo.java | 81 ++++++++++++++++++++++
7 files changed, 264 insertions(+), 6 deletions(-)
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/annotations/StaticActivationContextValue.java b/tapestry-core/src/main/java/org/apache/tapestry5/annotations/StaticActivationContextValue.java
new file mode 100644
index 0000000..c87fd44
--- /dev/null
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/annotations/StaticActivationContextValue.java
@@ -0,0 +1,47 @@
+// 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.tapestry5.annotations;
+
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static org.apache.tapestry5.ioc.annotations.AnnotationUseContext.PAGE;
+import static org.apache.tapestry5.ioc.annotations.AnnotationUseContext.COMPONENT;
+import static org.apache.tapestry5.ioc.annotations.AnnotationUseContext.MIXIN;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import org.apache.tapestry5.ioc.annotations.UseWith;
+
+/**
+ * Annotation that may be placed on parameters of event handler methods to define them
+ * as having a static value. If, in a given request, the activation context value for that
+ * parameter doesn't match the static value, the event handler method won't be called.
+ * This is particularly useful for REST endpoint event handler methods in pages.
+ *
+ * @since 5.8.0
+ */
+@Target(
+{ PARAMETER })
+@Retention(RUNTIME)
+@Documented
+@UseWith(
+{ PAGE, COMPONENT, MIXIN })
+public @interface StaticActivationContextValue
+{
+ /**
+ * The activation context value, case sensitive. Empty values or ones with spaces are forbidden.
+ */
+ String value();
+}
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/OnEventWorker.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/OnEventWorker.java
index 8436b26..9b570e2 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/OnEventWorker.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/OnEventWorker.java
@@ -16,6 +16,7 @@ import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.regex.Pattern;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.EventContext;
@@ -23,6 +24,7 @@ import org.apache.tapestry5.ValueEncoder;
import org.apache.tapestry5.annotations.OnEvent;
import org.apache.tapestry5.annotations.PublishEvent;
import org.apache.tapestry5.annotations.RequestParameter;
+import org.apache.tapestry5.annotations.StaticActivationContextValue;
import org.apache.tapestry5.annotations.DisableStrictChecks;
import org.apache.tapestry5.commons.internal.util.TapestryException;
import org.apache.tapestry5.commons.util.CollectionFactory;
@@ -48,6 +50,7 @@ import org.apache.tapestry5.plastic.LocalVariableCallback;
import org.apache.tapestry5.plastic.MethodAdvice;
import org.apache.tapestry5.plastic.MethodDescription;
import org.apache.tapestry5.plastic.MethodInvocation;
+import org.apache.tapestry5.plastic.MethodParameter;
import org.apache.tapestry5.plastic.PlasticClass;
import org.apache.tapestry5.plastic.PlasticField;
import org.apache.tapestry5.plastic.PlasticMethod;
@@ -74,8 +77,6 @@ public class OnEventWorker implements ComponentClassTransformWorker2
private final OperationTracker operationTracker;
- private final boolean componentIdCheck = true;
-
private final InstructionBuilderCallback RETURN_TRUE = new InstructionBuilderCallback()
{
public void doBuild(InstructionBuilder builder)
@@ -167,6 +168,8 @@ public class OnEventWorker implements ComponentClassTransformWorker2
boolean handleActivationEventContext = false;
+ final String[] staticActivationContextValues;
+
final PublishEvent publishEvent;
EventHandlerMethod(PlasticMethod method)
@@ -184,6 +187,40 @@ public class OnEventWorker implements ComponentClassTransformWorker2
componentId = extractComponentId(methodName, onEvent);
publishEvent = method.getAnnotation(PublishEvent.class);
+ staticActivationContextValues = extractStaticActivationContextValues(method);
+ }
+
+ final private Pattern WHITESPACE = Pattern.compile(".*\\s.*");
+
+ private String[] extractStaticActivationContextValues(PlasticMethod method)
+ {
+ String[] values = null;
+ for (int i = 0; i < method.getParameters().size(); i++)
+ {
+ MethodParameter parameter = method.getParameters().get(i);
+ final StaticActivationContextValue staticValue = parameter.getAnnotation(StaticActivationContextValue.class);
+ if (staticValue != null)
+ {
+ if (values == null)
+ {
+ values = new String[method.getParameters().size()];
+ }
+ String value = staticValue.value();
+ if (value != null && !value.isEmpty() && !WHITESPACE.matcher(value).matches())
+ {
+ values[i] = value;
+ }
+ else
+ {
+ throw new RuntimeException(String.format("%s has at least one parameter "
+ + "with a @%s annotation with an invalid value (empty string or "
+ + "value containing whitespace)",
+ method.getMethodIdentifier(),
+ StaticActivationContextValue.class.getSimpleName()));
+ }
+ }
+ }
+ return values;
}
void buildMatchAndInvocation(InstructionBuilder builder, final LocalVariable resultVariable)
@@ -192,8 +229,21 @@ public class OnEventWorker implements ComponentClassTransformWorker2
parameterSource == null ? null
: method.getPlasticClass().introduceField(EventHandlerMethodParameterSource.class, description.methodName + "$parameterSource").inject(parameterSource);
+ final PlasticField staticActivationContextValueField =
+ staticActivationContextValues == null ? null
+ : method.getPlasticClass().introduceField(String[].class, description.methodName + "$staticActivationContextValues").inject(staticActivationContextValues);
+
builder.loadArgument(0).loadConstant(eventType).loadConstant(componentId).loadConstant(minContextValues);
- builder.invoke(ComponentEvent.class, boolean.class, "matches", String.class, String.class, int.class);
+ if (staticActivationContextValueField != null)
+ {
+ builder.loadThis().getField(staticActivationContextValueField);
+ }
+ else
+ {
+ builder.loadNull();
+ }
+
+ builder.invoke(ComponentEvent.class, boolean.class, "matches", String.class, String.class, int.class, String[].class);
builder.when(Condition.NON_ZERO, new InstructionBuilderCallback()
{
@@ -374,13 +424,13 @@ public class OnEventWorker implements ComponentClassTransformWorker2
{
if (eventHandlerMethod.publishEvent != null)
{
- publishEvents.put(eventHandlerMethod.eventType.toLowerCase());
+ publishEvents.add(eventHandlerMethod.eventType.toLowerCase());
}
}
// If we do have events to publish, we apply the mixin and pass
// event information to it.
- if (publishEvents.length() > 0) {
+ if (publishEvents.size() > 0) {
model.addMixinClassName(PublishServerSideEvents.class.getName(), "after:*");
model.setMeta(InternalConstants.PUBLISH_COMPONENT_EVENTS_META, publishEvents.toString());
}
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/runtime/ComponentEvent.java b/tapestry-core/src/main/java/org/apache/tapestry5/runtime/ComponentEvent.java
index 050f1cd..e7e21fe 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/runtime/ComponentEvent.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/runtime/ComponentEvent.java
@@ -36,10 +36,45 @@ public interface ComponentEvent extends Event
* @param parameterCount
* minimum number of context values
* @return true if the event matches (and has not yet been aborted)
+ * @since 5.8.0
*/
boolean matches(String eventType, String componentId, int parameterCount);
/**
+ * Returns true if the event matches the provided criteria and the event has not yet been aborted.
+ *
+ * @param eventType
+ * the type of event (case insensitive match)
+ * @param componentId
+ * component is to match against (case insensitive), or the empty string
+ * @param parameterCount
+ * minimum number of context values
+ * @param staticActivationContextValues
+ * a String array. If null, there are no static activation context values. If
+ * any value in the array is null, it's considered dynamic and ignored.
+ * I any value in the arra isn't null, it's compared to the corresponding
+ * activation context value. If it doesn't match, this method will return null.
+ * @return true if the event matches (and has not yet been aborted)
+ */
+ default boolean matches(String eventType, String componentId, int parameterCount, String[] staticActivationContextValues)
+ {
+ boolean matches = matches(eventType, componentId, parameterCount);
+ if (matches && staticActivationContextValues != null)
+ {
+ for (int i = 0; i < staticActivationContextValues.length; i++)
+ {
+ if (staticActivationContextValues[i] != null &&
+ !staticActivationContextValues[i].equals(getEventContext().get(String.class, i)))
+ {
+ matches = false;
+ break;
+ }
+ }
+ }
+ return matches;
+ }
+
+ /**
* 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.
*
diff --git a/tapestry-core/src/test/app1/StaticActivationContextValueDemo.tml b/tapestry-core/src/test/app1/StaticActivationContextValueDemo.tml
new file mode 100644
index 0000000..609b85c
--- /dev/null
+++ b/tapestry-core/src/test/app1/StaticActivationContextValueDemo.tml
@@ -0,0 +1,27 @@
+<html t:type="Border" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd">
+<h1>@StaticActivationContextValue Demo</h1>
+
+ <p>
+ This is way more of a test for <code>@StaticActivationContextValue</code>,
+ which is targeted at implementing multiple REST endpoints in a single component page
+ than an example of usage. Changing page or component
+ state is better done with <code>EventLink</code>.
+ </p>
+
+ <p>
+ State: <span id="state">${state}</span>
+ </p>
+
+ <p>
+ <a t:type="PageLink" t:page="staticActivationContextValueDemo" t:context="completed" id="completed">Mark as completed</a>
+ </p>
+
+ <p>
+ <a t:type="PageLink" t:page="staticActivationContextValueDemo" t:context="closed" id="closed">Mark as closed</a>
+ </p>
+
+ <p>
+ <a t:type="PageLink" t:page="staticActivationContextValueDemo" t:context="reset" id="reset">Reset state</a>
+ </p>
+
+</html>
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/MiscTests.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/MiscTests.groovy
index 77f521c..8856ab1 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/MiscTests.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/integration/app1/MiscTests.groovy
@@ -95,5 +95,21 @@ class MiscTests extends App1TestCase {
// it matches the name of a formal parameter of AltTitle.
assertTextPresent "Parameter(s) 'AltTitle.title' are required for org.apache.tapestry5.corelib.components.ActionLink, but have not been bound."
}
+
+ @Test
+ void static_activation_context_value() {
+ openLinks "@StaticActivationContextValue Demo"
+ assertText "state", "none"
+
+ click "completed"
+ assertText "state", "completed"
+
+ click "closed"
+ assertText "state", "closed"
+
+ click "reset"
+ assertText "state", "none"
+
+ }
}
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java
index 4af960f..d2fbd68 100644
--- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java
@@ -59,7 +59,9 @@ public class Index
.newList(
new Item("PublishEventDemo", "@PublishEvent Demo", "Publishing server-side events to client-side code (JavaScript)"),
-
+
+ new Item("StaticActivationContextValueDemo", "@StaticActivationContextValue Demo", "Demonstrates the usage of @StaticActivationContextValue"),
+
new Item("Html5DateFieldDemo", "Html5DateField Demo", "Choosing dates using the native HTML5 date picker"),
// new Item("ZoneFormDemo", "Zone Form Decoration", "Fields inside an Ajax-updatd Form are still decorated properly."),
diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/StaticActivationContextValueDemo.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/StaticActivationContextValueDemo.java
new file mode 100644
index 0000000..ed57644
--- /dev/null
+++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/StaticActivationContextValueDemo.java
@@ -0,0 +1,81 @@
+// Copyright 2011 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.
+
+// 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.tapestry5.integration.app1.pages;
+
+import org.apache.tapestry5.EventConstants;
+import org.apache.tapestry5.annotations.OnEvent;
+import org.apache.tapestry5.annotations.Property;
+import org.apache.tapestry5.annotations.StaticActivationContextValue;
+
+public class StaticActivationContextValueDemo
+{
+ @Property
+ private String state;
+
+ @Property
+ final private String COMPLETED = "completed";
+
+ @Property
+ final private String CLOSED = "closed";
+
+ @Property
+ final private String RESET = "reset";
+
+ @Property
+ final private String NONE = "none";
+
+ @OnEvent(EventConstants.ACTIVATE)
+ void completed(@StaticActivationContextValue(COMPLETED) String ignored)
+ {
+ state = COMPLETED;
+ }
+
+ @OnEvent(EventConstants.ACTIVATE)
+ void closed(@StaticActivationContextValue(CLOSED) String ignored)
+ {
+ state = CLOSED;
+ }
+
+ @OnEvent(EventConstants.ACTIVATE)
+ void reset(@StaticActivationContextValue(RESET) String ignored)
+ {
+ state = NONE;
+ }
+
+ void onActivate()
+ {
+ if (state == null)
+ {
+ state = NONE;
+ }
+ }
+
+ public Object onPassivate()
+ {
+ return state != null && !NONE.equals(state) ? new String[] {state} : null;
+ }
+
+}