This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch WW-3691-executor in repository https://gitbox.apache.org/repos/asf/struts.git
commit 47398e8a784deb9a71e5f84ddfc3dbe1e5f2f8d4 Author: Lukasz Lenart <[email protected]> AuthorDate: Tue Oct 4 10:12:52 2022 +0200 WW-3691 Converts BackgroundProcess into interface and uses Executor to execute BackgroundProcess --- .../showcase/wait/ThreadPoolExecutorProvider.java | 56 +++++++++++ apps/showcase/src/main/resources/struts-wait.xml | 3 + .../interceptor/ExecuteAndWaitInterceptor.java | 66 +++++++++---- .../interceptor/exec/BackgroundProcess.java | 41 ++++++++ .../struts2/interceptor/exec/ExecutorProvider.java | 38 ++++++++ .../StrutsBackgroundProcess.java} | 77 ++++++++++----- .../interceptor/exec/StrutsExecutorProvider.java | 53 +++++++++++ .../interceptor/ExecuteAndWaitInterceptorTest.java | 46 ++++++++- .../StrutsBackgroundProcessTest.java} | 104 +++++++++++++++++---- 9 files changed, 419 insertions(+), 65 deletions(-) diff --git a/apps/showcase/src/main/java/org/apache/struts2/showcase/wait/ThreadPoolExecutorProvider.java b/apps/showcase/src/main/java/org/apache/struts2/showcase/wait/ThreadPoolExecutorProvider.java new file mode 100644 index 000000000..ffffc7a1d --- /dev/null +++ b/apps/showcase/src/main/java/org/apache/struts2/showcase/wait/ThreadPoolExecutorProvider.java @@ -0,0 +1,56 @@ +/* + * 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.showcase.wait; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.struts2.interceptor.exec.ExecutorProvider; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class ThreadPoolExecutorProvider implements ExecutorProvider { + + private static final Logger LOG = LogManager.getLogger(ThreadPoolExecutorProvider.class); + + private final ExecutorService executor; + + public ThreadPoolExecutorProvider() { + this.executor = new ThreadPoolExecutor(1, 2, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>()); + } + + @Override + public void execute(Runnable task) { + LOG.info("Executing task: {}", task); + executor.execute(task); + } + + @Override + public boolean isShutdown() { + return executor.isShutdown(); + } + + @Override + public void shutdown() { + LOG.info("Shutting down executor"); + executor.shutdown(); + } +} diff --git a/apps/showcase/src/main/resources/struts-wait.xml b/apps/showcase/src/main/resources/struts-wait.xml index 8ede40d74..7b6a204a6 100644 --- a/apps/showcase/src/main/resources/struts-wait.xml +++ b/apps/showcase/src/main/resources/struts-wait.xml @@ -24,6 +24,9 @@ "https://struts.apache.org/dtds/struts-2.5.dtd"> <struts> + + <bean type="org.apache.struts2.interceptor.exec.ExecutorProvider" class="org.apache.struts2.showcase.wait.ThreadPoolExecutorProvider"/> + <package name="wait" extends="struts-default" namespace="/wait"> <action name="example1"> diff --git a/core/src/main/java/org/apache/struts2/interceptor/ExecuteAndWaitInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/ExecuteAndWaitInterceptor.java index b49ebcae7..4a5cb91b4 100644 --- a/core/src/main/java/org/apache/struts2/interceptor/ExecuteAndWaitInterceptor.java +++ b/core/src/main/java/org/apache/struts2/interceptor/ExecuteAndWaitInterceptor.java @@ -22,12 +22,17 @@ import com.opensymphony.xwork2.Action; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.ActionProxy; +import com.opensymphony.xwork2.config.entities.ResultConfig; import com.opensymphony.xwork2.inject.Container; import com.opensymphony.xwork2.inject.Inject; import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.struts2.ServletActionContext; +import org.apache.struts2.interceptor.exec.BackgroundProcess; +import org.apache.struts2.interceptor.exec.ExecutorProvider; +import org.apache.struts2.interceptor.exec.StrutsBackgroundProcess; +import org.apache.struts2.interceptor.exec.StrutsExecutorProvider; import org.apache.struts2.util.TokenHelper; import org.apache.struts2.views.freemarker.FreemarkerResult; @@ -84,7 +89,7 @@ import java.util.Map; * <!-- END SNIPPET: description --> * * <p><u>Interceptor parameters:</u></p> - * + * <p> * <!-- START SNIPPET: parameters --> * * <ul> @@ -94,11 +99,11 @@ import java.util.Map; * <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> * * </ul> - * + * <p> * <!-- END SNIPPET: parameters --> * * <p><u>Extending the interceptor:</u></p> - * + * <p> * <!-- START SNIPPET: extending --> * <p> * If you wish to make special preparations before and/or after the invocation of the background thread, you can extend @@ -167,9 +172,8 @@ import java.util.Map; * <result name="success">longRunningAction-success.jsp</result> * </action> * </pre> - * + * <p> * <!-- END SNIPPET: example --> - * */ public class ExecuteAndWaitInterceptor extends MethodFilterInterceptor { @@ -186,22 +190,28 @@ public class ExecuteAndWaitInterceptor extends MethodFilterInterceptor { private int threadPriority = Thread.NORM_PRIORITY; private Container container; + private ExecutorProvider executor; @Inject public void setContainer(Container container) { this.container = container; } + @Inject(required = false) + public void setExecutorProvider(ExecutorProvider executorProvider) { + this.executor = executorProvider; + } + /** * Creates a new background process * - * @param name The process name + * @param name The process name * @param actionInvocation The action invocation - * @param threadPriority The thread priority + * @param threadPriority The thread priority * @return The new process */ protected BackgroundProcess getNewBackgroundProcess(String name, ActionInvocation actionInvocation, int threadPriority) { - return new BackgroundProcess(name + "BackgroundThread", actionInvocation, threadPriority); + return new StrutsBackgroundProcess(actionInvocation, name + "_background-process", threadPriority); } /** @@ -209,7 +219,6 @@ public class ExecuteAndWaitInterceptor extends MethodFilterInterceptor { * are mapped to requests. * * @param proxy action proxy - * * @return the name of the background thread */ protected String getBackgroundProcessName(ActionProxy proxy) { @@ -223,10 +232,10 @@ public class ExecuteAndWaitInterceptor extends MethodFilterInterceptor { ActionProxy proxy = actionInvocation.getProxy(); String name = getBackgroundProcessName(proxy); ActionContext context = actionInvocation.getInvocationContext(); - Map session = context.getSession(); + Map<String, Object> session = context.getSession(); HttpSession httpSession = ServletActionContext.getRequest().getSession(true); - Boolean secondTime = true; + Boolean secondTime = true; if (executeAfterValidationPass) { secondTime = (Boolean) context.get(KEY); if (secondTime == null) { @@ -250,8 +259,13 @@ public class ExecuteAndWaitInterceptor extends MethodFilterInterceptor { } if ((!executeAfterValidationPass || secondTime) && bp == null) { - bp = getNewBackgroundProcess(name, actionInvocation, threadPriority); + bp = getNewBackgroundProcess(name, actionInvocation, threadPriority).prepare(); session.put(KEY + name, bp); + if (executor.isShutdown()) { + LOG.warn("Executor is shutting down, cannot execute a new process"); + return actionInvocation.invoke(); + } + executor.execute(bp); performInitialDelay(bp); // first time let some time pass before showing wait page secondTime = false; } @@ -259,16 +273,16 @@ public class ExecuteAndWaitInterceptor extends MethodFilterInterceptor { if ((!executeAfterValidationPass || !secondTime) && bp != null && !bp.isDone()) { actionInvocation.getStack().push(bp.getAction()); - final String token = TokenHelper.getToken(); - if (token != null) { - TokenHelper.setSessionToken(TokenHelper.getTokenName(), token); + final String token = TokenHelper.getToken(); + if (token != null) { + TokenHelper.setSessionToken(TokenHelper.getTokenName(), token); } - Map results = proxy.getConfig().getResults(); + Map<String, ResultConfig> results = proxy.getConfig().getResults(); if (!results.containsKey(WAIT)) { - LOG.warn("ExecuteAndWait interceptor has detected that no result named 'wait' is available. " + - "Defaulting to a plain built-in wait page. It is highly recommend you " + - "provide an action-specific or global result named '{}'.", WAIT); + LOG.warn("ExecuteAndWait interceptor has detected that no result named 'wait' is available. " + + "Defaulting to a plain built-in wait page. It is highly recommend you " + + "provide an action-specific or global result named '{}'.", WAIT); // no wait result? hmm -- let's try to do dynamically put it in for you! //we used to add a fake "wait" result here, since the configuration is unmodifiable, that is no longer @@ -286,7 +300,7 @@ public class ExecuteAndWaitInterceptor extends MethodFilterInterceptor { session.remove(KEY + name); actionInvocation.getStack().push(bp.getAction()); - // if an exception occured during action execution, throw it here + // if an exception occurred during action execution, throw it here if (bp.getException() != null) { throw bp.getException(); } @@ -369,5 +383,17 @@ public class ExecuteAndWaitInterceptor extends MethodFilterInterceptor { this.executeAfterValidationPass = executeAfterValidationPass; } + @Override + public void init() { + super.init(); + if (executor == null) { + executor = new StrutsExecutorProvider(); + } + } + @Override + public void destroy() { + super.destroy(); + executor.shutdown(); + } } diff --git a/core/src/main/java/org/apache/struts2/interceptor/exec/BackgroundProcess.java b/core/src/main/java/org/apache/struts2/interceptor/exec/BackgroundProcess.java new file mode 100644 index 000000000..732c3d0af --- /dev/null +++ b/core/src/main/java/org/apache/struts2/interceptor/exec/BackgroundProcess.java @@ -0,0 +1,41 @@ +/* + * 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.interceptor.exec; + +import com.opensymphony.xwork2.ActionInvocation; +import org.apache.struts2.interceptor.ExecuteAndWaitInterceptor; + +/** + * Interface used to create a background process which will be executed by + * {@link ExecuteAndWaitInterceptor} + */ +public interface BackgroundProcess extends Runnable { + + BackgroundProcess prepare(); + + Object getAction(); + + ActionInvocation getInvocation(); + + String getResult(); + + Exception getException(); + + boolean isDone(); +} diff --git a/core/src/main/java/org/apache/struts2/interceptor/exec/ExecutorProvider.java b/core/src/main/java/org/apache/struts2/interceptor/exec/ExecutorProvider.java new file mode 100644 index 000000000..a6b06bde9 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/interceptor/exec/ExecutorProvider.java @@ -0,0 +1,38 @@ +/* + * 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.interceptor.exec; + +import java.util.concurrent.ExecutorService; + +/** + * Interface mimics {@link ExecutorService} to be used with + * {@link org.apache.struts2.interceptor.ExecuteAndWaitInterceptor} + * to execute {@link BackgroundProcess} + * + * @since 6.1.0 + */ +public interface ExecutorProvider { + + void execute(Runnable task); + + boolean isShutdown(); + + void shutdown(); + +} diff --git a/core/src/main/java/org/apache/struts2/interceptor/BackgroundProcess.java b/core/src/main/java/org/apache/struts2/interceptor/exec/StrutsBackgroundProcess.java similarity index 65% rename from core/src/main/java/org/apache/struts2/interceptor/BackgroundProcess.java rename to core/src/main/java/org/apache/struts2/interceptor/exec/StrutsBackgroundProcess.java index eed1811e0..1b29129b8 100644 --- a/core/src/main/java/org/apache/struts2/interceptor/BackgroundProcess.java +++ b/core/src/main/java/org/apache/struts2/interceptor/exec/StrutsBackgroundProcess.java @@ -16,21 +16,25 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.struts2.interceptor; - -import java.io.Serializable; +package org.apache.struts2.interceptor.exec; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionInvocation; +import org.apache.struts2.interceptor.exec.BackgroundProcess; + +import java.io.Serializable; /** * Background thread to be executed by the ExecuteAndWaitInterceptor. - * */ -public class BackgroundProcess implements Serializable { +public class StrutsBackgroundProcess implements BackgroundProcess, Serializable { private static final long serialVersionUID = 3884464776311686443L; + private final String threadName; + private final int threadPriority; + + transient private Thread processThread; //WW-4900 transient since 2.5.15 transient protected ActionInvocation invocation; transient protected Exception exception; @@ -41,32 +45,47 @@ public class BackgroundProcess implements Serializable { /** * Constructs a background process * - * @param threadName The thread name * @param invocation The action invocation - * @param threadPriority The thread priority + * @param threadName The name of background thread + * @param threadPriority The priority of background thread */ - public BackgroundProcess(String threadName, final ActionInvocation invocation, int threadPriority) { + public StrutsBackgroundProcess(ActionInvocation invocation, String threadName, int threadPriority) { this.invocation = invocation; + this.threadName = threadName; + this.threadPriority = threadPriority; + } + + @Override + public BackgroundProcess prepare() { try { - final Thread t = new Thread(new Runnable() { - public void run() { - try { - beforeInvocation(); - result = invocation.invokeActionOnly(); - afterInvocation(); - } catch (Exception e) { - exception = e; - } - - done = true; + processThread = new Thread(() -> { + try { + beforeInvocation(); + result = invocation.invokeActionOnly(); + afterInvocation(); + } catch (Exception e) { + exception = e; } + + done = true; }); - t.setName(threadName); - t.setPriority(threadPriority); - t.start(); + processThread.setName(threadName); + processThread.setPriority(threadPriority); } catch (Exception e) { + done = true; exception = e; } + return this; + } + + @Override + public void run() { + if (processThread == null) { + done = true; + exception = new IllegalStateException("Background thread " + threadName + " has not been prepared!"); + return; + } + processThread.start(); } /** @@ -93,8 +112,9 @@ public class BackgroundProcess implements Serializable { /** * Retrieves the action. * - * @return the action. + * @return the action. */ + @Override public Object getAction() { return invocation.getAction(); } @@ -104,6 +124,7 @@ public class BackgroundProcess implements Serializable { * * @return the action invocation */ + @Override public ActionInvocation getInvocation() { return invocation; } @@ -111,8 +132,9 @@ public class BackgroundProcess implements Serializable { /** * Gets the result of the background process. * - * @return the result; <tt>null</tt> if not done. + * @return the result; <tt>null</tt> if not done. */ + @Override public String getResult() { return result; } @@ -122,6 +144,7 @@ public class BackgroundProcess implements Serializable { * * @return the exception or <tt>null</tt> if no exception was thrown. */ + @Override public Exception getException() { return exception; } @@ -131,7 +154,13 @@ public class BackgroundProcess implements Serializable { * * @return <tt>true</tt> if finished, <tt>false</tt> otherwise */ + @Override public boolean isDone() { return done; } + + @Override + public String toString() { + return "StrutsBackgroundProcess { name = " + processThread.getName() + " }"; + } } diff --git a/core/src/main/java/org/apache/struts2/interceptor/exec/StrutsExecutorProvider.java b/core/src/main/java/org/apache/struts2/interceptor/exec/StrutsExecutorProvider.java new file mode 100644 index 000000000..7370f318a --- /dev/null +++ b/core/src/main/java/org/apache/struts2/interceptor/exec/StrutsExecutorProvider.java @@ -0,0 +1,53 @@ +/* + * 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.interceptor.exec; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class StrutsExecutorProvider implements ExecutorProvider { + + private static final Logger LOG = LogManager.getLogger(StrutsExecutorProvider.class); + + private final ExecutorService executor; + + public StrutsExecutorProvider() { + this.executor = Executors.newSingleThreadExecutor(); + } + + @Override + public void execute(Runnable task) { + LOG.debug("Executing task: {}", task); + executor.execute(task); + } + + @Override + public boolean isShutdown() { + return executor.isShutdown(); + } + + @Override + public void shutdown() { + LOG.debug("Shutting down executor"); + executor.shutdown(); + } +} diff --git a/core/src/test/java/org/apache/struts2/interceptor/ExecuteAndWaitInterceptorTest.java b/core/src/test/java/org/apache/struts2/interceptor/ExecuteAndWaitInterceptorTest.java index 08c034b9b..87595d9b5 100644 --- a/core/src/test/java/org/apache/struts2/interceptor/ExecuteAndWaitInterceptorTest.java +++ b/core/src/test/java/org/apache/struts2/interceptor/ExecuteAndWaitInterceptorTest.java @@ -36,9 +36,9 @@ import com.opensymphony.xwork2.interceptor.ParametersInterceptor; import com.opensymphony.xwork2.mock.MockResult; import com.opensymphony.xwork2.ognl.OgnlUtil; import com.opensymphony.xwork2.util.location.LocatableProperties; -import org.apache.struts2.ServletActionContext; import org.apache.struts2.StrutsInternalTestCase; import org.apache.struts2.dispatcher.HttpParameters; +import org.apache.struts2.interceptor.exec.ExecutorProvider; import org.apache.struts2.views.jsp.StrutsMockHttpServletRequest; import org.apache.struts2.views.jsp.StrutsMockHttpSession; @@ -49,6 +49,8 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; /** * Test case for ExecuteAndWaitInterceptor. @@ -78,6 +80,20 @@ public class ExecuteAndWaitInterceptorTest extends StrutsInternalTestCase { assertEquals("success", result2); } + public void testExecutorProvider() throws Exception { + waitInterceptor.setExecutorProvider(new TestExecutorProvider()); + + ActionProxy proxy = buildProxy("action1"); + String result = proxy.execute(); + assertEquals("wait", result); + + Thread.sleep(1000); + + ActionProxy proxy2 = buildProxy("action1"); + String result2 = proxy2.execute(); + assertEquals("success", result2); + } + public void testTwoWait() throws Exception { waitInterceptor.setDelay(0); waitInterceptor.setDelaySleepInterval(0); @@ -226,7 +242,11 @@ public class ExecuteAndWaitInterceptorTest extends StrutsInternalTestCase { .withServletRequest(request) .getContextMap(); + container.inject(waitInterceptor); container.inject(parametersInterceptor); + + waitInterceptor.init(); + parametersInterceptor.init(); } protected void tearDown() throws Exception { @@ -250,8 +270,6 @@ public class ExecuteAndWaitInterceptorTest extends StrutsInternalTestCase { } public void loadPackages() throws ConfigurationException { - - // interceptors waitInterceptor = new ExecuteAndWaitInterceptor(); parametersInterceptor = new ParametersInterceptor(); @@ -273,5 +291,27 @@ public class ExecuteAndWaitInterceptorTest extends StrutsInternalTestCase { } } + } +class TestExecutorProvider implements ExecutorProvider { + + private final ExecutorService executor = Executors.newSingleThreadExecutor(); + + @Override + public void execute(Runnable task) { + executor.execute(task); + } + + @Override + public boolean isShutdown() { + return executor.isShutdown(); + } + + @Override + public void shutdown() { + executor.shutdown(); + } +} + + diff --git a/core/src/test/java/org/apache/struts2/interceptor/BackgroundProcessTest.java b/core/src/test/java/org/apache/struts2/interceptor/exec/StrutsBackgroundProcessTest.java similarity index 50% rename from core/src/test/java/org/apache/struts2/interceptor/BackgroundProcessTest.java rename to core/src/test/java/org/apache/struts2/interceptor/exec/StrutsBackgroundProcessTest.java index b811b1dd5..68572c8d1 100644 --- a/core/src/test/java/org/apache/struts2/interceptor/BackgroundProcessTest.java +++ b/core/src/test/java/org/apache/struts2/interceptor/exec/StrutsBackgroundProcessTest.java @@ -16,10 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.struts2.interceptor; +package org.apache.struts2.interceptor.exec; import com.mockobjects.servlet.MockHttpServletRequest; import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.mock.MockActionInvocation; import org.apache.struts2.StrutsInternalTestCase; @@ -27,31 +28,43 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * Test case for BackgroundProcessTest. */ -public class BackgroundProcessTest extends StrutsInternalTestCase { +public class StrutsBackgroundProcessTest extends StrutsInternalTestCase { + + private final ExecutorService executor = Executors.newSingleThreadExecutor(); public void testSerializeDeserialize() throws Exception { final NotSerializableException expectedException = new NotSerializableException(new MockHttpServletRequest()); final Semaphore lock = new Semaphore(1); lock.acquire(); - MockActionInvocationWithActionInvoker invocation = new MockActionInvocationWithActionInvoker(new Callable<String>() { - @Override - public String call() throws Exception { - lock.release(); - throw expectedException; - } + MockActionInvocationWithActionInvoker invocation = new MockActionInvocationWithActionInvoker(() -> { + lock.release(); + throw expectedException; }); invocation.setInvocationContext(ActionContext.getContext()); - BackgroundProcess bp = new BackgroundProcess("BackgroundProcessTest.testSerializeDeserialize", invocation - , Thread.MIN_PRIORITY); - if(!lock.tryAcquire(1500L, TimeUnit.MILLISECONDS)) { + StrutsBackgroundProcess bp = (StrutsBackgroundProcess) new StrutsBackgroundProcess( + invocation, + "BackgroundProcessTest.testSerializeDeserialize", + Thread.MIN_PRIORITY + ) + .prepare(); + executor.execute(bp); + + if (!lock.tryAcquire(1500L, TimeUnit.MILLISECONDS)) { lock.release(); fail("background thread did not release lock on timeout"); } @@ -66,12 +79,12 @@ public class BackgroundProcessTest extends StrutsInternalTestCase { ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(bp); oos.close(); - byte b[] = baos.toByteArray(); + byte[] b = baos.toByteArray(); baos.close(); ByteArrayInputStream bais = new ByteArrayInputStream(b); ObjectInputStream ois = new ObjectInputStream(bais); - BackgroundProcess deserializedBp = (BackgroundProcess) ois.readObject(); + StrutsBackgroundProcess deserializedBp = (StrutsBackgroundProcess) ois.readObject(); ois.close(); bais.close(); @@ -81,11 +94,44 @@ public class BackgroundProcessTest extends StrutsInternalTestCase { assertEquals(bp.done, deserializedBp.done); } + public void testMultipleProcesses() throws InterruptedException { + MockActionInvocationWithActionInvoker invocation = new MockActionInvocationWithActionInvoker(() -> { + Thread.sleep(150); + return "done"; + }); + + List<BackgroundProcess> bps = new ArrayList<>(); + for (int i = 0; i < 10000; i++) { + String name = String.format("Order: %s", i); + BackgroundProcess bp = new LockBackgroundProcess(invocation, name).prepare(); + bps.add(bp); + executor.execute(bp); + } - private class MockActionInvocationWithActionInvoker extends MockActionInvocation { - private Callable<String> actionInvoker; + Thread.sleep(150); - MockActionInvocationWithActionInvoker(Callable<String> actionInvoker){ + for (BackgroundProcess bp : bps) { + assertTrue("Process is still active: " + bp.toString(), bp.isDone()); + } + } + + public void testUnpreparedProcess() throws ExecutionException, InterruptedException, TimeoutException { + // given + MockActionInvocationWithActionInvoker invocation = new MockActionInvocationWithActionInvoker(() -> "done"); + BackgroundProcess bp = new StrutsBackgroundProcess(invocation, "Unprepared", Thread.NORM_PRIORITY); + + // when + executor.submit(bp).get(1000, TimeUnit.MILLISECONDS); + + // then + assertTrue(bp.isDone()); + assertEquals("Background thread Unprepared has not been prepared!", bp.getException().getMessage()); + } + + private static class MockActionInvocationWithActionInvoker extends MockActionInvocation { + private final Callable<String> actionInvoker; + + MockActionInvocationWithActionInvoker(Callable<String> actionInvoker) { this.actionInvoker = actionInvoker; } @@ -95,10 +141,32 @@ public class BackgroundProcessTest extends StrutsInternalTestCase { } } - private class NotSerializableException extends Exception { + private static class NotSerializableException extends Exception { private MockHttpServletRequest notSerializableField; + NotSerializableException(MockHttpServletRequest notSerializableField) { this.notSerializableField = notSerializableField; } } -} \ No newline at end of file + +} + +class LockBackgroundProcess extends StrutsBackgroundProcess { + + private final Object lock = LockBackgroundProcess.class; + + public LockBackgroundProcess(ActionInvocation invocation, String name) { + super(invocation, name, Thread.NORM_PRIORITY); + } + + @Override + public void run() { + synchronized (lock) { + try { + super.run(); + } finally { + lock.notify(); + } + } + } +}
