Skip to content

Commit 8dd7631

Browse files
committed
WW-3691 Converts BackgroundProcess into interface and uses Executor to execute BackgroundProcess
1 parent 58f287b commit 8dd7631

File tree

10 files changed

+512
-151
lines changed

10 files changed

+512
-151
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.struts2.showcase.wait;
20+
21+
import org.apache.logging.log4j.LogManager;
22+
import org.apache.logging.log4j.Logger;
23+
import org.apache.struts2.interceptor.exec.ExecutorProvider;
24+
25+
import java.util.concurrent.ExecutorService;
26+
import java.util.concurrent.LinkedBlockingDeque;
27+
import java.util.concurrent.ThreadPoolExecutor;
28+
import java.util.concurrent.TimeUnit;
29+
30+
public class ThreadPoolExecutorProvider implements ExecutorProvider {
31+
32+
private static final Logger LOG = LogManager.getLogger(ThreadPoolExecutorProvider.class);
33+
34+
private final ExecutorService executor;
35+
36+
public ThreadPoolExecutorProvider() {
37+
this.executor = new ThreadPoolExecutor(1, 2, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>());
38+
}
39+
40+
@Override
41+
public void execute(Runnable task) {
42+
LOG.info("Executing task: {}", task);
43+
executor.execute(task);
44+
}
45+
46+
@Override
47+
public boolean isShutdown() {
48+
return executor.isShutdown();
49+
}
50+
51+
@Override
52+
public void shutdown() {
53+
LOG.info("Shutting down executor");
54+
executor.shutdown();
55+
}
56+
}

apps/showcase/src/main/resources/struts-wait.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
"https://struts.apache.org/dtds/struts-2.5.dtd">
2525

2626
<struts>
27+
28+
<bean type="org.apache.struts2.interceptor.exec.ExecutorProvider" class="org.apache.struts2.showcase.wait.ThreadPoolExecutorProvider"/>
29+
2730
<package name="wait" extends="struts-default" namespace="/wait">
2831

2932
<action name="example1">

core/src/main/java/org/apache/struts2/interceptor/ExecuteAndWaitInterceptor.java

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,17 @@
2222
import com.opensymphony.xwork2.ActionContext;
2323
import com.opensymphony.xwork2.ActionInvocation;
2424
import com.opensymphony.xwork2.ActionProxy;
25+
import com.opensymphony.xwork2.config.entities.ResultConfig;
2526
import com.opensymphony.xwork2.inject.Container;
2627
import com.opensymphony.xwork2.inject.Inject;
2728
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
2829
import org.apache.logging.log4j.LogManager;
2930
import org.apache.logging.log4j.Logger;
3031
import org.apache.struts2.ServletActionContext;
32+
import org.apache.struts2.interceptor.exec.BackgroundProcess;
33+
import org.apache.struts2.interceptor.exec.ExecutorProvider;
34+
import org.apache.struts2.interceptor.exec.StrutsBackgroundProcess;
35+
import org.apache.struts2.interceptor.exec.StrutsExecutorProvider;
3136
import org.apache.struts2.util.TokenHelper;
3237
import org.apache.struts2.views.freemarker.FreemarkerResult;
3338

@@ -84,7 +89,7 @@
8489
* <!-- END SNIPPET: description -->
8590
*
8691
* <p><u>Interceptor parameters:</u></p>
87-
*
92+
* <p>
8893
* <!-- START SNIPPET: parameters -->
8994
*
9095
* <ul>
@@ -94,11 +99,11 @@
9499
* <li>delaySleepInterval (optional) - only used with delay. Used for waking up at certain intervals to check if the background process is already done. Default is 100 millis.</li>
95100
*
96101
* </ul>
97-
*
102+
* <p>
98103
* <!-- END SNIPPET: parameters -->
99104
*
100105
* <p><u>Extending the interceptor:</u></p>
101-
*
106+
* <p>
102107
* <!-- START SNIPPET: extending -->
103108
* <p>
104109
* If you wish to make special preparations before and/or after the invocation of the background thread, you can extend
@@ -167,9 +172,8 @@
167172
* &lt;result name="success"&gt;longRunningAction-success.jsp&lt;/result&gt;
168173
* &lt;/action&gt;
169174
* </pre>
170-
*
175+
* <p>
171176
* <!-- END SNIPPET: example -->
172-
*
173177
*/
174178
public class ExecuteAndWaitInterceptor extends MethodFilterInterceptor {
175179

@@ -186,30 +190,35 @@ public class ExecuteAndWaitInterceptor extends MethodFilterInterceptor {
186190
private int threadPriority = Thread.NORM_PRIORITY;
187191

188192
private Container container;
193+
private ExecutorProvider executor;
189194

190195
@Inject
191196
public void setContainer(Container container) {
192197
this.container = container;
193198
}
194199

200+
@Inject(required = false)
201+
public void setExecutorProvider(ExecutorProvider executorProvider) {
202+
this.executor = executorProvider;
203+
}
204+
195205
/**
196206
* Creates a new background process
197207
*
198-
* @param name The process name
208+
* @param name The process name
199209
* @param actionInvocation The action invocation
200-
* @param threadPriority The thread priority
210+
* @param threadPriority The thread priority
201211
* @return The new process
202212
*/
203213
protected BackgroundProcess getNewBackgroundProcess(String name, ActionInvocation actionInvocation, int threadPriority) {
204-
return new BackgroundProcess(name + "BackgroundThread", actionInvocation, threadPriority);
214+
return new StrutsBackgroundProcess(actionInvocation, name + "_background-process", threadPriority);
205215
}
206216

207217
/**
208218
* Returns the name to associate the background process. Override to change the way background processes
209219
* are mapped to requests.
210220
*
211221
* @param proxy action proxy
212-
*
213222
* @return the name of the background thread
214223
*/
215224
protected String getBackgroundProcessName(ActionProxy proxy) {
@@ -223,10 +232,10 @@ protected String doIntercept(ActionInvocation actionInvocation) throws Exception
223232
ActionProxy proxy = actionInvocation.getProxy();
224233
String name = getBackgroundProcessName(proxy);
225234
ActionContext context = actionInvocation.getInvocationContext();
226-
Map session = context.getSession();
235+
Map<String, Object> session = context.getSession();
227236
HttpSession httpSession = ServletActionContext.getRequest().getSession(true);
228237

229-
Boolean secondTime = true;
238+
Boolean secondTime = true;
230239
if (executeAfterValidationPass) {
231240
secondTime = (Boolean) context.get(KEY);
232241
if (secondTime == null) {
@@ -250,25 +259,30 @@ protected String doIntercept(ActionInvocation actionInvocation) throws Exception
250259
}
251260

252261
if ((!executeAfterValidationPass || secondTime) && bp == null) {
253-
bp = getNewBackgroundProcess(name, actionInvocation, threadPriority);
262+
bp = getNewBackgroundProcess(name, actionInvocation, threadPriority).prepare();
254263
session.put(KEY + name, bp);
264+
if (executor.isShutdown()) {
265+
LOG.warn("Executor is shutting down, cannot execute a new process");
266+
return actionInvocation.invoke();
267+
}
268+
executor.execute(bp);
255269
performInitialDelay(bp); // first time let some time pass before showing wait page
256270
secondTime = false;
257271
}
258272

259273
if ((!executeAfterValidationPass || !secondTime) && bp != null && !bp.isDone()) {
260274
actionInvocation.getStack().push(bp.getAction());
261275

262-
final String token = TokenHelper.getToken();
263-
if (token != null) {
264-
TokenHelper.setSessionToken(TokenHelper.getTokenName(), token);
276+
final String token = TokenHelper.getToken();
277+
if (token != null) {
278+
TokenHelper.setSessionToken(TokenHelper.getTokenName(), token);
265279
}
266280

267-
Map results = proxy.getConfig().getResults();
281+
Map<String, ResultConfig> results = proxy.getConfig().getResults();
268282
if (!results.containsKey(WAIT)) {
269-
LOG.warn("ExecuteAndWait interceptor has detected that no result named 'wait' is available. " +
270-
"Defaulting to a plain built-in wait page. It is highly recommend you " +
271-
"provide an action-specific or global result named '{}'.", WAIT);
283+
LOG.warn("ExecuteAndWait interceptor has detected that no result named 'wait' is available. " +
284+
"Defaulting to a plain built-in wait page. It is highly recommend you " +
285+
"provide an action-specific or global result named '{}'.", WAIT);
272286
// no wait result? hmm -- let's try to do dynamically put it in for you!
273287

274288
//we used to add a fake "wait" result here, since the configuration is unmodifiable, that is no longer
@@ -286,7 +300,7 @@ protected String doIntercept(ActionInvocation actionInvocation) throws Exception
286300
session.remove(KEY + name);
287301
actionInvocation.getStack().push(bp.getAction());
288302

289-
// if an exception occured during action execution, throw it here
303+
// if an exception occurred during action execution, throw it here
290304
if (bp.getException() != null) {
291305
throw bp.getException();
292306
}
@@ -369,5 +383,17 @@ public void setExecuteAfterValidationPass(boolean executeAfterValidationPass) {
369383
this.executeAfterValidationPass = executeAfterValidationPass;
370384
}
371385

386+
@Override
387+
public void init() {
388+
super.init();
389+
if (executor == null) {
390+
executor = new StrutsExecutorProvider();
391+
}
392+
}
372393

394+
@Override
395+
public void destroy() {
396+
super.destroy();
397+
executor.shutdown();
398+
}
373399
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.struts2.interceptor.exec;
20+
21+
import com.opensymphony.xwork2.ActionInvocation;
22+
import org.apache.struts2.interceptor.ExecuteAndWaitInterceptor;
23+
24+
/**
25+
* Interface used to create a background process which will be executed by
26+
* {@link ExecuteAndWaitInterceptor}
27+
*/
28+
public interface BackgroundProcess extends Runnable {
29+
30+
BackgroundProcess prepare();
31+
32+
Object getAction();
33+
34+
ActionInvocation getInvocation();
35+
36+
String getResult();
37+
38+
Exception getException();
39+
40+
boolean isDone();
41+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.struts2.interceptor.exec;
20+
21+
import java.util.concurrent.ExecutorService;
22+
23+
/**
24+
* Interface mimics {@link ExecutorService} to be used with
25+
* {@link org.apache.struts2.interceptor.ExecuteAndWaitInterceptor}
26+
* to execute {@link BackgroundProcess}
27+
*
28+
* @since 6.1.0
29+
*/
30+
public interface ExecutorProvider {
31+
32+
void execute(Runnable task);
33+
34+
boolean isShutdown();
35+
36+
void shutdown();
37+
38+
}

0 commit comments

Comments
 (0)