mirror of
https://github.com/apache/struts.git
synced 2026-08-07 07:37:20 +00:00
WW-4874 Introduces Servlet3 plugin by adding support for async action methods
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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 com.opensymphony.xwork2;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* Adds support for invoke async actions. This allows us to support action methods that return {@link Callable}
|
||||
* as well as invoking them in separate not-container thread then executing the result in another container thread.
|
||||
*
|
||||
* @since 2.5.14
|
||||
*/
|
||||
public interface AsyncManager {
|
||||
boolean hasAsyncActionResult();
|
||||
|
||||
Object getAsyncActionResult();
|
||||
|
||||
void invokeAsyncAction(Callable asyncAction);
|
||||
}
|
||||
@@ -40,6 +40,7 @@ import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* The Default ActionInvocation implementation
|
||||
@@ -71,6 +72,8 @@ public class DefaultActionInvocation implements ActionInvocation {
|
||||
protected Container container;
|
||||
protected UnknownHandlerManager unknownHandlerManager;
|
||||
protected OgnlUtil ognlUtil;
|
||||
protected AsyncManager asyncManager;
|
||||
protected Callable asyncAction;
|
||||
protected WithLazyParams.LazyParamInjector lazyParamInjector;
|
||||
|
||||
public DefaultActionInvocation(final Map<String, Object> extraContext, final boolean pushAction) {
|
||||
@@ -108,6 +111,11 @@ public class DefaultActionInvocation implements ActionInvocation {
|
||||
this.ognlUtil = ognlUtil;
|
||||
}
|
||||
|
||||
@Inject(required=false)
|
||||
public void setAsyncManager(AsyncManager asyncManager) {
|
||||
this.asyncManager = asyncManager;
|
||||
}
|
||||
|
||||
public Object getAction() {
|
||||
return action;
|
||||
}
|
||||
@@ -237,49 +245,61 @@ public class DefaultActionInvocation implements ActionInvocation {
|
||||
throw new IllegalStateException("Action has already executed");
|
||||
}
|
||||
|
||||
if (interceptors.hasNext()) {
|
||||
final InterceptorMapping interceptorMapping = interceptors.next();
|
||||
String interceptorMsg = "interceptorMapping: " + interceptorMapping.getName();
|
||||
UtilTimerStack.push(interceptorMsg);
|
||||
try {
|
||||
Interceptor interceptor = interceptorMapping.getInterceptor();
|
||||
if (interceptor instanceof WithLazyParams) {
|
||||
interceptor = lazyParamInjector.injectParams(interceptor, interceptorMapping.getParams(), invocationContext);
|
||||
if (asyncManager == null || !asyncManager.hasAsyncActionResult()) {
|
||||
if (interceptors.hasNext()) {
|
||||
final InterceptorMapping interceptorMapping = interceptors.next();
|
||||
String interceptorMsg = "interceptorMapping: " + interceptorMapping.getName();
|
||||
UtilTimerStack.push(interceptorMsg);
|
||||
try {
|
||||
Interceptor interceptor = interceptorMapping.getInterceptor();
|
||||
if (interceptor instanceof WithLazyParams) {
|
||||
interceptor = lazyParamInjector.injectParams(interceptor, interceptorMapping.getParams(), invocationContext);
|
||||
}
|
||||
resultCode = interceptor.intercept(DefaultActionInvocation.this);
|
||||
} finally {
|
||||
UtilTimerStack.pop(interceptorMsg);
|
||||
}
|
||||
resultCode = interceptor.intercept(DefaultActionInvocation.this);
|
||||
} finally {
|
||||
UtilTimerStack.pop(interceptorMsg);
|
||||
} else {
|
||||
resultCode = invokeActionOnly();
|
||||
}
|
||||
} else {
|
||||
resultCode = invokeActionOnly();
|
||||
Object asyncActionResult = asyncManager.getAsyncActionResult();
|
||||
if (asyncActionResult instanceof Throwable) {
|
||||
throw new Exception((Throwable) asyncActionResult);
|
||||
}
|
||||
asyncAction = null;
|
||||
resultCode = saveResult(proxy.getConfig(), asyncActionResult);
|
||||
}
|
||||
|
||||
// this is needed because the result will be executed, then control will return to the Interceptor, which will
|
||||
// return above and flow through again
|
||||
if (!executed) {
|
||||
if (preResultListeners != null) {
|
||||
LOG.trace("Executing PreResultListeners for result [{}]", result);
|
||||
if (asyncManager == null || asyncAction == null) {
|
||||
// this is needed because the result will be executed, then control will return to the Interceptor, which will
|
||||
// return above and flow through again
|
||||
if (!executed) {
|
||||
if (preResultListeners != null) {
|
||||
LOG.trace("Executing PreResultListeners for result [{}]", result);
|
||||
|
||||
for (Object preResultListener : preResultListeners) {
|
||||
PreResultListener listener = (PreResultListener) preResultListener;
|
||||
for (Object preResultListener : preResultListeners) {
|
||||
PreResultListener listener = (PreResultListener) preResultListener;
|
||||
|
||||
String _profileKey = "preResultListener: ";
|
||||
try {
|
||||
UtilTimerStack.push(_profileKey);
|
||||
listener.beforeResult(this, resultCode);
|
||||
}
|
||||
finally {
|
||||
UtilTimerStack.pop(_profileKey);
|
||||
String _profileKey = "preResultListener: ";
|
||||
try {
|
||||
UtilTimerStack.push(_profileKey);
|
||||
listener.beforeResult(this, resultCode);
|
||||
} finally {
|
||||
UtilTimerStack.pop(_profileKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now execute the result, if we're supposed to
|
||||
if (proxy.getExecuteResult()) {
|
||||
executeResult();
|
||||
}
|
||||
// now execute the result, if we're supposed to
|
||||
if (proxy.getExecuteResult()) {
|
||||
executeResult();
|
||||
}
|
||||
|
||||
executed = true;
|
||||
executed = true;
|
||||
}
|
||||
} else {
|
||||
asyncManager.invokeAsyncAction(asyncAction);
|
||||
}
|
||||
|
||||
return resultCode;
|
||||
@@ -495,6 +515,9 @@ public class DefaultActionInvocation implements ActionInvocation {
|
||||
// Wire the result automatically
|
||||
container.inject(explicitResult);
|
||||
return null;
|
||||
} else if (methodResult instanceof Callable) {
|
||||
asyncAction = (Callable) methodResult;
|
||||
return null;
|
||||
} else {
|
||||
return (String) methodResult;
|
||||
}
|
||||
|
||||
@@ -561,8 +561,16 @@ public class Dispatcher {
|
||||
String name = mapping.getName();
|
||||
String method = mapping.getMethod();
|
||||
|
||||
ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
|
||||
namespace, name, method, extraContext, true, false);
|
||||
ActionProxy proxy;
|
||||
|
||||
//check if we are probably in an async resuming
|
||||
ActionInvocation inv = ActionContext.getContext().getActionInvocation();
|
||||
if (inv == null || inv.isExecuted()) {
|
||||
proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name, method,
|
||||
extraContext, true, false);
|
||||
} else {
|
||||
proxy = inv.getProxy();
|
||||
}
|
||||
|
||||
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
|
||||
|
||||
|
||||
@@ -79,9 +79,12 @@ public class PrepareOperations {
|
||||
// detected existing context, so we are probably in a forward
|
||||
ctx = new ActionContext(new HashMap<>(oldContext.getContextMap()));
|
||||
} else {
|
||||
ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
|
||||
stack.getContext().putAll(dispatcher.createContextMap(request, response, null));
|
||||
ctx = new ActionContext(stack.getContext());
|
||||
ctx = ServletActionContext.getActionContext(request); //checks if we are probably in an async
|
||||
if (ctx == null) {
|
||||
ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
|
||||
stack.getContext().putAll(dispatcher.createContextMap(request, response, null));
|
||||
ctx = new ActionContext(stack.getContext());
|
||||
}
|
||||
}
|
||||
request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
|
||||
ActionContext.setContext(ctx);
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
<module>spring</module>
|
||||
<module>testng</module>
|
||||
<module>tiles</module>
|
||||
<module>servlet3</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
*/
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.apache.struts</groupId>
|
||||
<artifactId>struts2-plugins</artifactId>
|
||||
<version>2.5.14-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>struts2-servlet3-plugin</artifactId>
|
||||
<name>Struts 2 Servlet3 Plugin</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>3.0.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.struts2.servlet3.async;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* A {@link Callable} with a timeout value and an {@link Executor}.
|
||||
*
|
||||
* @since 2.5.14
|
||||
*/
|
||||
public class AsyncAction implements Callable {
|
||||
private Callable callable;
|
||||
private Long timeout;
|
||||
private Executor executor;
|
||||
|
||||
public AsyncAction(Callable callable) {
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
public AsyncAction(long timeout, Callable callable) {
|
||||
this(callable);
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public AsyncAction(Executor executor, Callable callable) {
|
||||
this(callable);
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
public AsyncAction(long timeout, Executor executor, Callable callable) {
|
||||
this(timeout, callable);
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
public Long getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public Executor getExecutor() {
|
||||
return executor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call() throws Exception {
|
||||
return callable.call();
|
||||
}
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.struts2.servlet3.async;
|
||||
|
||||
import com.opensymphony.xwork2.AsyncManager;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.struts2.ServletActionContext;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.AsyncEvent;
|
||||
import javax.servlet.AsyncListener;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Implements {@link AsyncManager} to add support for invoke async actions via Servlet 3's API.
|
||||
*
|
||||
* @since 2.5.14
|
||||
*/
|
||||
public class Servlet3AsyncManager implements AsyncManager, AsyncListener {
|
||||
private static final Logger LOG = LogManager.getLogger(Servlet3AsyncManager.class);
|
||||
private static final AtomicInteger threadCount = new AtomicInteger(0);
|
||||
|
||||
private AsyncContext asyncContext;
|
||||
private boolean asyncActionStarted;
|
||||
private Boolean asyncCompleted;
|
||||
private Object asyncActionResult;
|
||||
|
||||
@Override
|
||||
public void invokeAsyncAction(final Callable asyncAction) {
|
||||
if (asyncActionStarted) {
|
||||
return;
|
||||
}
|
||||
|
||||
Long timeout = null;
|
||||
Executor executor = null;
|
||||
if (asyncAction instanceof AsyncAction) {
|
||||
AsyncAction customAsyncAction = (AsyncAction) asyncAction;
|
||||
timeout = customAsyncAction.getTimeout();
|
||||
executor = customAsyncAction.getExecutor();
|
||||
}
|
||||
|
||||
HttpServletRequest req = ServletActionContext.getRequest();
|
||||
asyncActionResult = null;
|
||||
asyncCompleted = false;
|
||||
|
||||
if (asyncContext == null || !req.isAsyncStarted()) {
|
||||
asyncContext = req.startAsync(req, ServletActionContext.getResponse());
|
||||
asyncContext.addListener(this);
|
||||
if (timeout != null) {
|
||||
asyncContext.setTimeout(timeout);
|
||||
}
|
||||
}
|
||||
asyncActionStarted = true;
|
||||
LOG.debug("Async processing started for " + asyncContext);
|
||||
|
||||
final Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
setAsyncActionResultAndDispatch(asyncAction.call());
|
||||
} catch (Throwable e) {
|
||||
setAsyncActionResultAndDispatch(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (executor != null) {
|
||||
executor.execute(task);
|
||||
} else {
|
||||
final Thread thread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
task.run();
|
||||
} finally {
|
||||
threadCount.decrementAndGet();
|
||||
}
|
||||
}
|
||||
}, this.getClass().getSimpleName() + "-" + threadCount.incrementAndGet());
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
|
||||
private void setAsyncActionResultAndDispatch(Object asyncActionResult) {
|
||||
this.asyncActionResult = asyncActionResult;
|
||||
|
||||
String log = "Async result [" + asyncActionResult + "] of " + asyncContext;
|
||||
if (asyncCompleted) {
|
||||
LOG.error(log + " - could not complete result executing due to timeout or network error");
|
||||
} else {
|
||||
LOG.debug(log + " - dispatching request to execute result in container");
|
||||
asyncContext.dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAsyncActionResult() {
|
||||
return asyncActionResult != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAsyncActionResult() {
|
||||
return asyncActionResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(AsyncEvent asyncEvent) throws IOException {
|
||||
asyncContext = null;
|
||||
asyncCompleted = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeout(AsyncEvent asyncEvent) throws IOException {
|
||||
LOG.debug("Processing timeout for " + asyncEvent.getAsyncContext());
|
||||
setAsyncActionResultAndDispatch("timeout");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(AsyncEvent asyncEvent) throws IOException {
|
||||
Throwable e = asyncEvent.getThrowable();
|
||||
LOG.error("Processing error for " + asyncEvent.getAsyncContext(), e);
|
||||
setAsyncActionResultAndDispatch(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!--
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
*/
|
||||
-->
|
||||
<!DOCTYPE struts PUBLIC
|
||||
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
|
||||
"http://struts.apache.org/dtds/struts-2.5.dtd">
|
||||
|
||||
<struts>
|
||||
<bean type="com.opensymphony.xwork2.AsyncManager" name="default"
|
||||
class="org.apache.struts2.servlet3.async.Servlet3AsyncManager" scope="prototype" />
|
||||
</struts>
|
||||
Reference in New Issue
Block a user