001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.jca.work; 018 019import java.util.concurrent.Callable; 020import java.util.concurrent.Future; 021import java.util.concurrent.FutureTask; 022 023import javax.naming.NamingException; 024import javax.resource.spi.BootstrapContext; 025import javax.resource.spi.work.ExecutionContext; 026import javax.resource.spi.work.Work; 027import javax.resource.spi.work.WorkException; 028import javax.resource.spi.work.WorkListener; 029import javax.resource.spi.work.WorkManager; 030import javax.resource.spi.work.WorkRejectedException; 031 032import org.springframework.beans.factory.InitializingBean; 033import org.springframework.core.task.AsyncListenableTaskExecutor; 034import org.springframework.core.task.TaskDecorator; 035import org.springframework.core.task.TaskRejectedException; 036import org.springframework.core.task.TaskTimeoutException; 037import org.springframework.jca.context.BootstrapContextAware; 038import org.springframework.jndi.JndiLocatorSupport; 039import org.springframework.lang.Nullable; 040import org.springframework.scheduling.SchedulingException; 041import org.springframework.scheduling.SchedulingTaskExecutor; 042import org.springframework.util.Assert; 043import org.springframework.util.concurrent.ListenableFuture; 044import org.springframework.util.concurrent.ListenableFutureTask; 045 046/** 047 * {@link org.springframework.core.task.TaskExecutor} implementation 048 * that delegates to a JCA 1.7 WorkManager, implementing the 049 * {@link javax.resource.spi.work.WorkManager} interface. 050 * 051 * <p>This is mainly intended for use within a JCA ResourceAdapter implementation, 052 * but may also be used in a standalone environment, delegating to a locally 053 * embedded WorkManager implementation (such as Geronimo's). 054 * 055 * <p>Also implements the JCA 1.7 WorkManager interface itself, delegating all 056 * calls to the target WorkManager. Hence, a caller can choose whether it wants 057 * to talk to this executor through the Spring TaskExecutor interface or the 058 * WorkManager interface. 059 * 060 * <p>This adapter is also capable of obtaining a JCA WorkManager from JNDI. 061 * This is for example appropriate on the Geronimo application server, where 062 * WorkManager GBeans (e.g. Geronimo's default "DefaultWorkManager" GBean) 063 * can be linked into the Java EE environment through "gbean-ref" entries 064 * in the {@code geronimo-web.xml} deployment descriptor. 065 * 066 * @author Juergen Hoeller 067 * @since 2.0.3 068 * @see #setWorkManager 069 * @see javax.resource.spi.work.WorkManager#scheduleWork 070 */ 071public class WorkManagerTaskExecutor extends JndiLocatorSupport 072 implements AsyncListenableTaskExecutor, SchedulingTaskExecutor, WorkManager, BootstrapContextAware, InitializingBean { 073 074 @Nullable 075 private WorkManager workManager; 076 077 @Nullable 078 private String workManagerName; 079 080 private boolean blockUntilStarted = false; 081 082 private boolean blockUntilCompleted = false; 083 084 @Nullable 085 private WorkListener workListener; 086 087 @Nullable 088 private TaskDecorator taskDecorator; 089 090 091 /** 092 * Create a new WorkManagerTaskExecutor, expecting bean-style configuration. 093 * @see #setWorkManager 094 */ 095 public WorkManagerTaskExecutor() { 096 } 097 098 /** 099 * Create a new WorkManagerTaskExecutor for the given WorkManager. 100 * @param workManager the JCA WorkManager to delegate to 101 */ 102 public WorkManagerTaskExecutor(WorkManager workManager) { 103 setWorkManager(workManager); 104 } 105 106 107 /** 108 * Specify the JCA WorkManager instance to delegate to. 109 */ 110 public void setWorkManager(WorkManager workManager) { 111 Assert.notNull(workManager, "WorkManager must not be null"); 112 this.workManager = workManager; 113 } 114 115 /** 116 * Set the JNDI name of the JCA WorkManager. 117 * <p>This can either be a fully qualified JNDI name, 118 * or the JNDI name relative to the current environment 119 * naming context if "resourceRef" is set to "true". 120 * @see #setWorkManager 121 * @see #setResourceRef 122 */ 123 public void setWorkManagerName(String workManagerName) { 124 this.workManagerName = workManagerName; 125 } 126 127 /** 128 * Specify the JCA BootstrapContext that contains the 129 * WorkManager to delegate to. 130 */ 131 @Override 132 public void setBootstrapContext(BootstrapContext bootstrapContext) { 133 Assert.notNull(bootstrapContext, "BootstrapContext must not be null"); 134 this.workManager = bootstrapContext.getWorkManager(); 135 } 136 137 /** 138 * Set whether to let {@link #execute} block until the work 139 * has been actually started. 140 * <p>Uses the JCA {@code startWork} operation underneath, 141 * instead of the default {@code scheduleWork}. 142 * @see javax.resource.spi.work.WorkManager#startWork 143 * @see javax.resource.spi.work.WorkManager#scheduleWork 144 */ 145 public void setBlockUntilStarted(boolean blockUntilStarted) { 146 this.blockUntilStarted = blockUntilStarted; 147 } 148 149 /** 150 * Set whether to let {@link #execute} block until the work 151 * has been completed. 152 * <p>Uses the JCA {@code doWork} operation underneath, 153 * instead of the default {@code scheduleWork}. 154 * @see javax.resource.spi.work.WorkManager#doWork 155 * @see javax.resource.spi.work.WorkManager#scheduleWork 156 */ 157 public void setBlockUntilCompleted(boolean blockUntilCompleted) { 158 this.blockUntilCompleted = blockUntilCompleted; 159 } 160 161 /** 162 * Specify a JCA WorkListener to apply, if any. 163 * <p>This shared WorkListener instance will be passed on to the 164 * WorkManager by all {@link #execute} calls on this TaskExecutor. 165 */ 166 public void setWorkListener(@Nullable WorkListener workListener) { 167 this.workListener = workListener; 168 } 169 170 /** 171 * Specify a custom {@link TaskDecorator} to be applied to any {@link Runnable} 172 * about to be executed. 173 * <p>Note that such a decorator is not necessarily being applied to the 174 * user-supplied {@code Runnable}/{@code Callable} but rather to the actual 175 * execution callback (which may be a wrapper around the user-supplied task). 176 * <p>The primary use case is to set some execution context around the task's 177 * invocation, or to provide some monitoring/statistics for task execution. 178 * <p><b>NOTE:</b> Exception handling in {@code TaskDecorator} implementations 179 * is limited to plain {@code Runnable} execution via {@code execute} calls. 180 * In case of {@code #submit} calls, the exposed {@code Runnable} will be a 181 * {@code FutureTask} which does not propagate any exceptions; you might 182 * have to cast it and call {@code Future#get} to evaluate exceptions. 183 * @since 4.3 184 */ 185 public void setTaskDecorator(TaskDecorator taskDecorator) { 186 this.taskDecorator = taskDecorator; 187 } 188 189 @Override 190 public void afterPropertiesSet() throws NamingException { 191 if (this.workManager == null) { 192 if (this.workManagerName != null) { 193 this.workManager = lookup(this.workManagerName, WorkManager.class); 194 } 195 else { 196 this.workManager = getDefaultWorkManager(); 197 } 198 } 199 } 200 201 /** 202 * Obtain a default WorkManager to delegate to. 203 * Called if no explicit WorkManager or WorkManager JNDI name has been specified. 204 * <p>The default implementation returns a {@link SimpleTaskWorkManager}. 205 * Can be overridden in subclasses. 206 */ 207 protected WorkManager getDefaultWorkManager() { 208 return new SimpleTaskWorkManager(); 209 } 210 211 private WorkManager obtainWorkManager() { 212 Assert.state(this.workManager != null, "No WorkManager specified"); 213 return this.workManager; 214 } 215 216 217 //------------------------------------------------------------------------- 218 // Implementation of the Spring SchedulingTaskExecutor interface 219 //------------------------------------------------------------------------- 220 221 @Override 222 public void execute(Runnable task) { 223 execute(task, TIMEOUT_INDEFINITE); 224 } 225 226 @Override 227 public void execute(Runnable task, long startTimeout) { 228 Work work = new DelegatingWork(this.taskDecorator != null ? this.taskDecorator.decorate(task) : task); 229 try { 230 if (this.blockUntilCompleted) { 231 if (startTimeout != TIMEOUT_INDEFINITE || this.workListener != null) { 232 obtainWorkManager().doWork(work, startTimeout, null, this.workListener); 233 } 234 else { 235 obtainWorkManager().doWork(work); 236 } 237 } 238 else if (this.blockUntilStarted) { 239 if (startTimeout != TIMEOUT_INDEFINITE || this.workListener != null) { 240 obtainWorkManager().startWork(work, startTimeout, null, this.workListener); 241 } 242 else { 243 obtainWorkManager().startWork(work); 244 } 245 } 246 else { 247 if (startTimeout != TIMEOUT_INDEFINITE || this.workListener != null) { 248 obtainWorkManager().scheduleWork(work, startTimeout, null, this.workListener); 249 } 250 else { 251 obtainWorkManager().scheduleWork(work); 252 } 253 } 254 } 255 catch (WorkRejectedException ex) { 256 if (WorkException.START_TIMED_OUT.equals(ex.getErrorCode())) { 257 throw new TaskTimeoutException("JCA WorkManager rejected task because of timeout: " + task, ex); 258 } 259 else { 260 throw new TaskRejectedException("JCA WorkManager rejected task: " + task, ex); 261 } 262 } 263 catch (WorkException ex) { 264 throw new SchedulingException("Could not schedule task on JCA WorkManager", ex); 265 } 266 } 267 268 @Override 269 public Future<?> submit(Runnable task) { 270 FutureTask<Object> future = new FutureTask<>(task, null); 271 execute(future, TIMEOUT_INDEFINITE); 272 return future; 273 } 274 275 @Override 276 public <T> Future<T> submit(Callable<T> task) { 277 FutureTask<T> future = new FutureTask<>(task); 278 execute(future, TIMEOUT_INDEFINITE); 279 return future; 280 } 281 282 @Override 283 public ListenableFuture<?> submitListenable(Runnable task) { 284 ListenableFutureTask<Object> future = new ListenableFutureTask<>(task, null); 285 execute(future, TIMEOUT_INDEFINITE); 286 return future; 287 } 288 289 @Override 290 public <T> ListenableFuture<T> submitListenable(Callable<T> task) { 291 ListenableFutureTask<T> future = new ListenableFutureTask<>(task); 292 execute(future, TIMEOUT_INDEFINITE); 293 return future; 294 } 295 296 297 //------------------------------------------------------------------------- 298 // Implementation of the JCA WorkManager interface 299 //------------------------------------------------------------------------- 300 301 @Override 302 public void doWork(Work work) throws WorkException { 303 obtainWorkManager().doWork(work); 304 } 305 306 @Override 307 public void doWork(Work work, long delay, ExecutionContext executionContext, WorkListener workListener) 308 throws WorkException { 309 310 obtainWorkManager().doWork(work, delay, executionContext, workListener); 311 } 312 313 @Override 314 public long startWork(Work work) throws WorkException { 315 return obtainWorkManager().startWork(work); 316 } 317 318 @Override 319 public long startWork(Work work, long delay, ExecutionContext executionContext, WorkListener workListener) 320 throws WorkException { 321 322 return obtainWorkManager().startWork(work, delay, executionContext, workListener); 323 } 324 325 @Override 326 public void scheduleWork(Work work) throws WorkException { 327 obtainWorkManager().scheduleWork(work); 328 } 329 330 @Override 331 public void scheduleWork(Work work, long delay, ExecutionContext executionContext, WorkListener workListener) 332 throws WorkException { 333 334 obtainWorkManager().scheduleWork(work, delay, executionContext, workListener); 335 } 336 337}