gt:             if (startupDuration < fudgeFactor)
context: /*******************************************************************************
   * Copyright (c) 1997, 2020 IBM Corporation and others.
   * All rights reserved. This program and the accompanying materials
   * are made available under the terms of the Eclipse Public License v1.0
   * which accompanies this distribution, and is available at
   * http://www.eclipse.org/legal/epl-v10.html
   *
   * Contributors:
   *     IBM Corporation - initial API and implementation
   *******************************************************************************/
  package com.ibm.ws.jca.internal;
  
  import java.io.Serializable;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.concurrent.BlockingQueue;
  import java.util.concurrent.Callable;
  import java.util.concurrent.ConcurrentLinkedQueue;
  import java.util.concurrent.LinkedBlockingQueue;
  import java.util.concurrent.RejectedExecutionException;
  import java.util.concurrent.TimeUnit;
  
  import javax.resource.ResourceException;
  import javax.resource.spi.ResourceAdapterAssociation;
  import javax.resource.spi.work.ExecutionContext;
  import javax.resource.spi.work.HintsContext;
  import javax.resource.spi.work.TransactionContext;
  import javax.resource.spi.work.Work;
  import javax.resource.spi.work.WorkCompletedException;
  import javax.resource.spi.work.WorkContext;
  import javax.resource.spi.work.WorkContextErrorCodes;
  import javax.resource.spi.work.WorkContextLifecycleListener;
  import javax.resource.spi.work.WorkContextProvider;
  import javax.resource.spi.work.WorkEvent;
  import javax.resource.spi.work.WorkException;
  import javax.resource.spi.work.WorkListener;
  import javax.resource.spi.work.WorkManager;
  import javax.resource.spi.work.WorkRejectedException;
  
  import com.ibm.websphere.ras.Tr;
  import com.ibm.websphere.ras.TraceComponent;
  import com.ibm.ws.ffdc.FFDCFilter;
  import com.ibm.ws.ffdc.annotation.FFDCIgnore;
  import com.ibm.ws.jca.security.JCASecurityContext;
  import com.ibm.wsspi.threadcontext.ThreadContext;
  import com.ibm.wsspi.threadcontext.ThreadContextDescriptor;
  import com.ibm.wsspi.threadcontext.WSContextService;
  import com.ibm.wsspi.threadcontext.jca.JCAContextProvider;
  
  /**
   * A wrapper for Work.
   * This wrapper takes care of execution context handling, work listener notification
   * and startTimeout.
   */
  public class WorkProxy implements Callable<Void>, Runnable {
      /**
       * Constructor for WorkProxy.
       *
       * @pre theWork != null
       *
       * @param theWork the actual Work object that is passed in by the RA.
       * @param theStartTimeout abort the request after this amount of time has passed
       *            and the actual Work has not yet started.
       *            A start timeout occurs when the thread pool is to busy
       *            to allocate a thread to start the work on.
       * @param theContext controls what context information is used by the thread to
       *            establish its context
       * @param theListener send state changes to the Work to this object.
       * @param bootstrapContext the bootstrap context.
       * @param runningWork list of work that is running.
       * @param applyDefaultContext determines whether or not to apply default context for thread context types that aren't otherwise specified or configured.
       * @throws ResourceException if unable to associate with the resource adapter.
       */
      public WorkProxy(
                       Work theWork,
                       long theStartTimeout,
                       ExecutionContext theContext,
                       WorkListener theListener,
                       BootstrapContextImpl bootstrapContext,
                       ConcurrentLinkedQueue<Work> runningWork,
                       boolean applyDefaultContext) throws Exception {
  
          work = theWork;
          startTimeout = theStartTimeout;
          executionContext = theContext;
          lsnr = theListener;
          this.bootstrapContext = bootstrapContext;
          this.runningWork = runningWork;
          // JCA 11.4
          // If the resource adapter returns a null or an empty List when the WorkManager
          // makes a call to the getWorkContexts method, the WorkManager must treat it as if no
          // additional execution contexts are associated with that Work instance and must
          // continue with the Work processing.
          boolean isWorkContextProvider = work instanceof WorkContextProvider;
          workContexts = isWorkContextProvider ? ((WorkContextProvider) work).getWorkContexts() : null;
  
          // RA must not submit a Work that implements WorkContextProvider along
          // with a valid, not null, ExecutionContext -- 11.5 p.3
          if (isWorkContextProvider && executionContext != null) {
              WorkRejectedException wrex = new WorkRejectedException(Utils.getMessage("J2CA8623.execution.context.conflict",
                                                                                      bootstrapContext.resourceAdapterID), WorkException.UNDEFINED);
              if (lsnr != null)
                  lsnr.workRejected(new WorkEvent(work, WorkEvent.WORK_REJECTED, work, wrex));
              throw wrex;
          }
  
          String workName = null;
          if (workContexts != null)
              for (WorkContext workContext : workContexts)
                  if (workContext instanceof HintsContext) {
                      // JCA 11.6
                      // The WorkManager must reject the establishment of the HintsContext if the values
                      // provided for the hints are not valid.
                      Map<String, Serializable> hints = ((HintsContext) workContext).getHints();
  
                      // JCA 11.6.1.1 Work Name Hint
                      // The value for the hint must be a valid java.lang.String.
                      Serializable value = hints.get(HintsContext.NAME_HINT);
                      if (value == null || value instanceof String)
                          workName = (String) value;
                      else
                          hintsContextSetupFailure = new ClassCastException(Tr.formatMessage(TC, "J2CA8687.hint.datatype.invalid", "HintsContext.NAME_HINT", String.class.getName(),
                                                                                             bootstrapContext.resourceAdapterID, value, value.getClass().getName()));
  
                      // JCA 11.6.1.2 Long-running Work instance Hint
                      // The value of the hint must be a valid boolean value (true or false).
                      value = hints.get(HintsContext.LONGRUNNING_HINT);
                      if (value instanceof Boolean) {
                          String key = bootstrapContext.eeVersion < 9 ? "javax.enterprise.concurrent.LONGRUNNING_HINT" : "jakarta.enterprise.concurrent.LONGRUNNING_HINT";
                          executionProperties.put(key, value.toString());
                      } else if (value != null)
                          hintsContextSetupFailure = new ClassCastException(Tr.formatMessage(TC, "J2CA8687.hint.datatype.invalid", "HintsContext.LONGRUNNING_HINT",
                                                                                             Boolean.class.getName(),
                                                                                             bootstrapContext.resourceAdapterID, value, value.getClass().getName()));
                  }
          String identityNameKey = bootstrapContext.eeVersion < 9 ? "javax.enterprise.concurrent.IDENTITY_NAME" : "jakarta.enterprise.concurrent.IDENTITY_NAME";
          executionProperties.put(identityNameKey, workName == null ? work == null ? null : work.getClass().getName() : workName);
          executionProperties.put(WSContextService.TASK_OWNER, bootstrapContext.resourceAdapterID);
          if (bootstrapContext.propagateThreadContext)
              if (applyDefaultContext)
                  executionProperties.put(WSContextService.DEFAULT_CONTEXT, WSContextService.UNCONFIGURED_CONTEXT_TYPES);
              else
                  executionProperties.put(WSContextService.SKIP_CONTEXT_PROVIDERS, "com.ibm.ws.transaction.context.provider");
          else
              executionProperties.put(WSContextService.DEFAULT_CONTEXT, WSContextService.ALL_CONTEXT_TYPES);
  
          // If task needs to be run with specified work context(s)
          threadContextDescriptor = bootstrapContext.contextSvc.captureThreadContext(executionProperties);
  
          if (work instanceof ResourceAdapterAssociation && bootstrapContext.resourceAdapter != null)
              ((ResourceAdapterAssociation) work).setResourceAdapter(bootstrapContext.resourceAdapter);
  
          // Send notice to listener that work has been accepted.
          // This notice should occur as soon as the work has been checked out
          // as acceptable and before the call to actually start the work occurs.
          //
          // Work is the correct thing to pass in for the first parameter.   This is because
          // EventObject provides the getSource method which returns an Object and so WorkEvent
          // must provide an Object to EventObject.
  
          if (lsnr != null) {
              WorkEvent event = new WorkEvent(work, WorkEvent.WORK_ACCEPTED, work, null);
              lsnr.workAccepted(event);
          }
  
          // Capturing the beginning time for the work should always be the last thing that
          // WorkProxy does so that the time between the creation of the proxy and the
          // call to run() is as small as possible.   Otherwise we may get more work
          // rejections due to small start time out values than we should
          timeAccepted = System.currentTimeMillis();
      }
  
      /**
       * Append work inflow context to the captured thread context.
       *
       * @return merged list of captured thread context and work inflow context.
       * @throws WorkCompletedException if work context is unacceptable.
       */
      private ThreadContextDescriptor appendInflowContext() throws WorkCompletedException {
          ThreadContextDescriptor merged = threadContextDescriptor.clone();
          Set<String> inflowContext = new HashSet<String>();
  
          // WorkContext
          if (workContexts != null)
              for (WorkContext workContext : workContexts)
                  if (workContext instanceof HintsContext) {
                      if (hintsContextSetupFailure != null)
                          throw contextSetupFailure(workContext, WorkContextErrorCodes.CONTEXT_SETUP_FAILED, hintsContextSetupFailure);
                  } else {
                      JCAContextProvider provider = bootstrapContext.getJCAContextProvider(workContext.getClass());
                      if (provider == null)
                          throw contextSetupFailure(workContext, WorkContextErrorCodes.UNSUPPORTED_CONTEXT_TYPE, null);
                      else {
                          ThreadContext context;
                          try {
                              context = provider.getInflowContext(workContext, executionProperties);
                          } catch (Throwable x) {
                              throw contextSetupFailure(workContext, WorkContextErrorCodes.CONTEXT_SETUP_FAILED, x);
                          }
                          String workContextProviderName = bootstrapContext.getJCAContextProviderName(workContext.getClass());
                          if (!inflowContext.add(workContextProviderName))
                              throw contextSetupFailure(workContext, WorkContextErrorCodes.DUPLICATE_CONTEXTS, null);
                          merged.set(workContextProviderName, context);
                      }
                  }
  
          // ExecutionContext
          if (executionContext != null)
              if (inflowContext.isEmpty() && (executionContext.getXid() != null || executionContext.getTransactionTimeout() != WorkManager.UNKNOWN)) {
                  JCAContextProvider provider = bootstrapContext.getJCAContextProvider(TransactionContext.class);
                  if (provider == null)
                      throw contextSetupFailure(executionContext, WorkContextErrorCodes.UNSUPPORTED_CONTEXT_TYPE, null);
                  ThreadContext context;
                  try {
                      context = provider.getInflowContext(executionContext, executionProperties);
                  } catch (Throwable x) {
                      throw contextSetupFailure(executionContext, WorkContextErrorCodes.CONTEXT_SETUP_FAILED, x);
                  }
                  String executionContextHandlerName = bootstrapContext.getJCAContextProviderName(TransactionContext.class);
                  merged.set(executionContextHandlerName, context);
              } // else - already checked for this error condition earlier
  
          return merged;
      }
  
      /**
       * @pre work != null
       * @see java.util.concurrent.Callable#call()
       */
      @FFDCIgnore(Throwable.class)
      @Override
      public Void call() throws WorkException {
          final boolean trace = TraceComponent.isAnyTracingEnabled();
          if (trace && TC.isEntryEnabled())
              Tr.entry(this, TC, "call", work, lsnr);
  
          // 1. Capture the current time before run does anything else
          long currentTime = System.currentTimeMillis();
          long startupDuration = currentTime - timeAccepted;
  
          // 2. Check to see if time is up before actually doing the work
          // There can be a delay between requesting that the ThreadPool execute the work
          // and the work actually beginning.  If this delay exceeds the startTimeout value,
          // then the work must be rejected with a WorkRejectedException
          if (startTimeout != WorkManager.INDEFINITE && startupDuration > startTimeout && startTimeout >= 0)
  