/**
   * Records deploy event.
   *
   * @param cls Deployed class.
   * @param clsLdr Class loader.
   * @param recordEvt Flag indicating whether to record events.
   */
  @SuppressWarnings({"unchecked"})
  private void recordDeployFailed(Class<?> cls, ClassLoader clsLdr, boolean recordEvt) {
    assert cls != null;
    assert clsLdr != null;

    boolean isTask = isTask(cls);

    String msg =
        "Failed to deploy "
            + (isTask ? "task" : "class")
            + " [cls="
            + cls
            + ", clsLdr="
            + clsLdr
            + ']';

    if (recordEvt
        && ctx.event().isRecordable(isTask ? EVT_CLASS_DEPLOY_FAILED : EVT_TASK_DEPLOY_FAILED)) {
      String taskName = isTask ? U.getTaskName((Class<? extends GridTask<?, ?>>) cls) : null;

      GridDeploymentEvent evt = new GridDeploymentEvent();

      evt.message(msg);
      evt.nodeId(ctx.localNodeId());
      evt.type(isTask(cls) ? EVT_CLASS_DEPLOY_FAILED : EVT_TASK_DEPLOY_FAILED);
      evt.alias(taskName);

      ctx.event().record(evt);
    }

    if (log.isInfoEnabled()) {
      log.info(msg);
    }
  }
  /** {@inheritDoc} */
  @Override
  public void start() throws GridException {
    ctxLdr = U.detectClassLoader(getClass());

    spi.setListener(new LocalDeploymentListener());

    if (log.isDebugEnabled()) {
      log.debug(startInfo());
    }
  }
  /**
   * Adds new participant to deployment.
   *
   * @param dep Shared deployment.
   * @param meta Request metadata.
   * @return {@code True} if participant was added.
   */
  private boolean addParticipant(SharedDeployment dep, GridDeploymentMetadata meta) {
    assert dep != null;
    assert meta != null;

    assert Thread.holdsLock(mux);

    if (!checkModeMatch(dep, meta)) return false;

    if (meta.participants() != null) {
      for (Map.Entry<UUID, GridTuple2<GridUuid, Long>> e : meta.participants().entrySet()) {
        dep.addParticipant(e.getKey(), e.getValue().get1(), e.getValue().get2());

        if (log.isDebugEnabled())
          log.debug(
              "Added new participant [nodeId="
                  + e.getKey()
                  + ", clsLdrId="
                  + e.getValue().get1()
                  + ", seqNum="
                  + e.getValue().get2()
                  + ']');
      }
    }

    if (dep.deployMode() == CONTINUOUS || meta.participants() == null) {
      if (!dep.addParticipant(meta.senderNodeId(), meta.classLoaderId(), meta.sequenceNumber())) {
        U.warn(
            log,
            "Failed to create shared mode deployment "
                + "(requested class loader was already undeployed, did sender node leave grid?) "
                + "[clsLdrId="
                + meta.classLoaderId()
                + ", senderNodeId="
                + meta.senderNodeId()
                + ']');

        return false;
      }

      if (log.isDebugEnabled())
        log.debug(
            "Added new participant [nodeId="
                + meta.senderNodeId()
                + ", clsLdrId="
                + meta.classLoaderId()
                + ", seqNum="
                + meta.sequenceNumber()
                + ']');
    }

    return true;
  }
  /**
   * Records deploy event.
   *
   * @param cls Deployed class.
   * @param alias Class alias.
   * @param recordEvt Flag indicating whether to record events.
   */
  private void recordDeploy(Class<?> cls, String alias, boolean recordEvt) {
    assert cls != null;

    boolean isTask = isTask(cls);

    String msg = (isTask ? "Task" : "Class") + " locally deployed: " + cls;

    if (recordEvt && ctx.event().isRecordable(isTask ? EVT_TASK_DEPLOYED : EVT_CLASS_DEPLOYED)) {
      GridDeploymentEvent evt = new GridDeploymentEvent();

      evt.message(msg);
      evt.nodeId(ctx.localNodeId());
      evt.type(isTask ? EVT_TASK_DEPLOYED : EVT_CLASS_DEPLOYED);
      evt.alias(alias);

      ctx.event().record(evt);
    }

    // Don't record JDK or Grid classes.
    if (U.isGrid(cls) || U.isJdk(cls)) return;

    if (log.isInfoEnabled()) log.info(msg);
  }
  /**
   * Checks if deployment modes match.
   *
   * @param dep Shared deployment.
   * @param meta Request metadata.
   * @return {@code True} if shared deployment modes match.
   */
  private boolean checkModeMatch(GridDeploymentInfo dep, GridDeploymentMetadata meta) {
    if (dep.deployMode() != meta.deploymentMode()) {
      U.warn(
          log,
          "Received invalid deployment mode (will not deploy, make sure that all nodes "
              + "executing the same classes in shared mode have identical GridDeploymentMode parameter) [mode="
              + meta.deploymentMode()
              + ", expected="
              + dep.deployMode()
              + ']');

      return false;
    }

    return true;
  }
  /**
   * @param depMode Deployment mode.
   * @param ldr Class loader to deploy.
   * @param cls Class.
   * @param alias Class alias.
   * @return Deployment.
   */
  @SuppressWarnings({"ConstantConditions"})
  private GridDeployment deploy(
      GridDeploymentMode depMode, ClassLoader ldr, Class<?> cls, String alias) {
    assert Thread.holdsLock(mux);

    LinkedList<GridDeployment> cachedDeps = null;

    GridDeployment dep = null;

    // Find existing class loader info.
    for (LinkedList<GridDeployment> deps : cache.values()) {
      for (GridDeployment d : deps) {
        if (d.classLoader() == ldr) {
          // Cache class and alias.
          d.addDeployedClass(cls, alias);

          cachedDeps = deps;

          dep = d;

          break;
        }
      }

      if (cachedDeps != null) {
        break;
      }
    }

    if (cachedDeps != null) {
      assert dep != null;

      cache.put(alias, cachedDeps);

      if (!cls.getName().equals(alias)) {
        // Cache by class name as well.
        cache.put(cls.getName(), cachedDeps);
      }

      return dep;
    }

    GridUuid ldrId = GridUuid.randomUuid();

    long seqNum = seq.incrementAndGet();

    String userVer = getUserVersion(ldr);

    dep = new GridDeployment(depMode, ldr, ldrId, seqNum, userVer, cls.getName(), true);

    dep.addDeployedClass(cls, alias);

    LinkedList<GridDeployment> deps =
        F.addIfAbsent(cache, alias, F.<GridDeployment>newLinkedList());

    if (!deps.isEmpty()) {
      for (GridDeployment d : deps) {
        if (!d.isUndeployed()) {
          U.error(
              log,
              "Found more than one active deployment for the same resource "
                  + "[cls="
                  + cls
                  + ", depMode="
                  + depMode
                  + ", dep="
                  + d
                  + ']');

          return null;
        }
      }
    }

    // Add at the beginning of the list for future fast access.
    deps.addFirst(dep);

    if (!cls.getName().equals(alias)) {
      // Cache by class name as well.
      cache.put(cls.getName(), deps);
    }

    if (log.isDebugEnabled()) {
      log.debug("Created new deployment: " + dep);
    }

    return dep;
  }
  /** {@inheritDoc} */
  @Nullable
  @SuppressWarnings({"UnusedCatchParameter"})
  @Override
  public GridDeployment getDeployment(GridDeploymentMetadata meta) {
    GridDeployment dep;

    Class<?> cls = null;

    String alias = meta.alias();

    synchronized (mux) {
      // Validate metadata.
      assert meta.alias() != null;

      dep = getDeployment(meta.alias());

      if (dep != null) {
        if (log.isDebugEnabled()) {
          log.debug("Acquired deployment class from local cache: " + dep);
        }

        return dep;
      }

      GridDeploymentResource rsrc = spi.findResource(meta.alias());

      if (rsrc != null) {
        dep =
            deploy(
                ctx.config().getDeploymentMode(),
                rsrc.getClassLoader(),
                rsrc.getResourceClass(),
                alias);

        if (dep == null) {
          return null;
        }

        if (log.isDebugEnabled()) {
          log.debug("Acquired deployment class from SPI: " + dep);
        }
      }
      // Auto-deploy.
      else {
        ClassLoader ldr = meta.classLoader();

        if (ldr == null) {
          ldr = Thread.currentThread().getContextClassLoader();

          // Safety.
          if (ldr == null) {
            ldr = ctxLdr;
          }
        }

        // Don't auto-deploy locally in case of nested execution.
        if (ldr instanceof GridDeploymentClassLoader) {
          return null;
        }

        try {
          // Check that class can be loaded.
          cls = ldr.loadClass(meta.alias());

          spi.register(ldr, cls);

          rsrc = spi.findResource(alias);

          if (rsrc != null && rsrc.getResourceClass().equals(cls)) {
            if (log.isDebugEnabled()) {
              log.debug("Retrieved auto-loaded resource from spi: " + rsrc);
            }

            dep = deploy(ctx.config().getDeploymentMode(), ldr, cls, alias);

            if (dep == null) {
              return null;
            }
          } else {
            U.warn(
                log,
                "Failed to find resource from deployment SPI even after registering it: "
                    + meta.alias());

            return null;
          }
        } catch (ClassNotFoundException e) {
          if (log.isDebugEnabled()) {
            log.debug(
                "Failed to load class for local auto-deployment [ldr="
                    + ldr
                    + ", meta="
                    + meta
                    + ']');
          }

          return null;
        } catch (GridSpiException e) {
          U.error(log, "Failed to deploy local class: " + meta.alias(), e);

          return null;
        }
      }
    }

    if (cls != null) {
      recordDeploy(cls, alias, meta.isRecord());

      dep.addDeployedClass(cls, meta.className(), meta.alias());
    }

    if (log.isDebugEnabled()) {
      log.debug("Acquired deployment class: " + dep);
    }

    return dep;
  }
  /**
   * Removes obsolete deployments in case of redeploy.
   *
   * @param meta Request metadata.
   * @return List of shares deployment.
   */
  private GridTuple2<Boolean, SharedDeployment> checkRedeploy(GridDeploymentMetadata meta) {
    assert Thread.holdsLock(mux);

    SharedDeployment newDep = null;

    for (List<SharedDeployment> deps : cache.values()) {
      for (SharedDeployment dep : deps) {
        if (!dep.isUndeployed() && !dep.isPendingUndeploy()) {
          long undeployTimeout = ctx.config().getNetworkTimeout();

          SharedDeployment doomed = null;

          // Only check deployments with no participants.
          if (!dep.hasParticipants()) {
            // In case of SHARED deployment it is possible to get hear if
            // unmarshalling happens during undeploy. In this case, we
            // simply don't do anything.
            if (dep.deployMode() == CONTINUOUS) {
              if (dep.existingDeployedClass(meta.className()) != null) {
                // Change from shared deploy to shared undeploy or user version change.
                // Simply remove all deployments with no participating nodes.
                if (meta.deploymentMode() == SHARED
                    || !meta.userVersion().equals(dep.userVersion())) doomed = dep;
              }
            }
          }
          // If there are participants, we undeploy if class loader ID on some node changed.
          else if (dep.existingDeployedClass(meta.className()) != null) {
            GridTuple2<GridUuid, Long> ldr = dep.getClassLoaderId(meta.senderNodeId());

            if (ldr != null) {
              if (!ldr.get1().equals(meta.classLoaderId())) {
                // If deployed sequence number is less, then schedule for undeployment.
                if (ldr.get2() < meta.sequenceNumber()) {
                  if (log.isDebugEnabled())
                    log.debug(
                        "Received request for a class with newer sequence number "
                            + "(will schedule current class for undeployment) [newSeq="
                            + meta.sequenceNumber()
                            + ", oldSeq="
                            + ldr.get2()
                            + ", senderNodeId="
                            + meta.senderNodeId()
                            + ", newClsLdrId="
                            + meta.classLoaderId()
                            + ", oldClsLdrId="
                            + ldr.get1()
                            + ']');

                  doomed = dep;
                } else if (ldr.get2() > meta.sequenceNumber()) {
                  long time = System.currentTimeMillis() - dep.timestamp();

                  if (newDep == null && time < ctx.config().getNetworkTimeout()) {
                    // Set undeployTimeout, so the class will be scheduled
                    // for undeployment.
                    undeployTimeout = ctx.config().getNetworkTimeout() - time;

                    if (log.isDebugEnabled())
                      log.debug(
                          "Received execution request for a stale class (will deploy and "
                              + "schedule undeployment in "
                              + undeployTimeout
                              + "ms) "
                              + "[curSeq="
                              + ldr.get2()
                              + ", staleSeq="
                              + meta.sequenceNumber()
                              + ", cls="
                              + meta.className()
                              + ", senderNodeId="
                              + meta.senderNodeId()
                              + ", curLdrId="
                              + ldr.get1()
                              + ", staleLdrId="
                              + meta.classLoaderId()
                              + ']');

                    // We got the redeployed class before the old one.
                    // Simply create a temporary deployment for the sender node,
                    // and schedule undeploy for it.
                    newDep = createNewDeployment(meta, false);

                    doomed = newDep;
                  } else {
                    U.warn(
                        log,
                        "Received execution request for a class that has been redeployed "
                            + "(will ignore): "
                            + meta.alias());

                    if (log.isDebugEnabled())
                      log.debug(
                          "Received execution request for a class that has been redeployed "
                              + "(will ignore) [alias="
                              + meta.alias()
                              + ", dep="
                              + dep
                              + ']');

                    return F.t(false, null);
                  }
                } else {
                  U.error(
                      log,
                      "Sequence number does not correspond to class loader ID [seqNum="
                          + meta.sequenceNumber()
                          + ", dep="
                          + dep
                          + ']');

                  return F.t(false, null);
                }
              }
            }
          }

          if (doomed != null) {
            doomed.onUndeployScheduled();

            if (log.isDebugEnabled()) log.debug("Deployment was scheduled for undeploy: " + doomed);

            // Lifespan time.
            final long endTime = System.currentTimeMillis() + undeployTimeout;

            // Deployment to undeploy.
            final SharedDeployment undep = doomed;

            ctx.timeout()
                .addTimeoutObject(
                    new GridTimeoutObject() {
                      @Override
                      public GridUuid timeoutId() {
                        return undep.classLoaderId();
                      }

                      @Override
                      public long endTime() {
                        return endTime < 0 ? Long.MAX_VALUE : endTime;
                      }

                      @Override
                      public void onTimeout() {
                        boolean removed = false;

                        // Hot redeployment.
                        synchronized (mux) {
                          assert undep.isPendingUndeploy();

                          if (!undep.isUndeployed()) {
                            undep.undeploy();

                            undep.onRemoved();

                            removed = true;

                            Collection<SharedDeployment> deps = cache.get(undep.userVersion());

                            if (deps != null) {
                              for (Iterator<SharedDeployment> i = deps.iterator(); i.hasNext(); )
                                if (i.next() == undep) i.remove();

                              if (deps.isEmpty()) cache.remove(undep.userVersion());
                            }

                            if (log.isInfoEnabled())
                              log.info(
                                  "Undeployed class loader due to deployment mode change, "
                                      + "user version change, or hot redeployment: "
                                      + undep);
                          }
                        }

                        // Outside synchronization.
                        if (removed) undep.recordUndeployed(null);
                      }
                    });
          }
        }
      }
    }

    if (newDep != null) {
      List<SharedDeployment> list =
          F.addIfAbsent(cache, meta.userVersion(), F.<SharedDeployment>newList());

      assert list != null;

      list.add(newDep);
    }

    return F.t(true, newDep);
  }
  /** {@inheritDoc} */
  @Override
  public void start() throws GridException {
    ctxLdr = U.detectClassLoader(getClass());

    if (log.isDebugEnabled()) log.debug(startInfo());
  }
  /** {@inheritDoc} */
  @Override
  public GridDeployment getDeployment(GridDeploymentMetadata meta) {
    assert meta != null;

    assert ctx.config().isPeerClassLoadingEnabled();

    // Validate metadata.
    assert meta.classLoaderId() != null;
    assert meta.senderNodeId() != null;
    assert meta.sequenceNumber() >= -1;
    assert meta.parentLoader() == null;

    if (log.isDebugEnabled())
      log.debug("Starting to peer-load class based on deployment metadata: " + meta);

    while (true) {
      List<SharedDeployment> depsToCheck = null;

      SharedDeployment dep = null;

      synchronized (mux) {
        // Check obsolete request.
        if (isDeadClassLoader(meta)) return null;

        List<SharedDeployment> deps = cache.get(meta.userVersion());

        if (deps != null) {
          assert !deps.isEmpty();

          for (SharedDeployment d : deps) {
            if (d.hasParticipant(meta.senderNodeId(), meta.classLoaderId())
                || meta.senderNodeId().equals(ctx.localNodeId())) {
              // Done.
              dep = d;

              break;
            }
          }

          if (dep == null) {
            GridTuple2<Boolean, SharedDeployment> redeployCheck = checkRedeploy(meta);

            if (!redeployCheck.get1()) {
              // Checking for redeployment encountered invalid state.
              if (log.isDebugEnabled())
                log.debug("Checking for redeployment encountered invalid state: " + meta);

              return null;
            }

            dep = redeployCheck.get2();

            if (dep == null) {
              // Find existing deployments that need to be checked
              // whether they should be reused for this request.
              for (SharedDeployment d : deps) {
                if (!d.isPendingUndeploy() && !d.isUndeployed()) {
                  if (depsToCheck == null) depsToCheck = new LinkedList<SharedDeployment>();

                  if (log.isDebugEnabled()) log.debug("Adding deployment to check: " + d);

                  depsToCheck.add(d);
                }
              }

              // If no deployment can be reused, create a new one.
              if (depsToCheck == null) {
                dep = createNewDeployment(meta, false);

                deps.add(dep);
              }
            }
          }
        } else {
          GridTuple2<Boolean, SharedDeployment> redeployCheck = checkRedeploy(meta);

          if (!redeployCheck.get1()) {
            // Checking for redeployment encountered invalid state.
            if (log.isDebugEnabled())
              log.debug("Checking for redeployment encountered invalid state: " + meta);

            return null;
          }

          dep = redeployCheck.get2();

          if (dep == null)
            // Create peer class loader.
            dep = createNewDeployment(meta, true);
        }
      }

      if (dep != null) {
        if (log.isDebugEnabled())
          log.debug("Found SHARED or CONTINUOUS deployment after first check: " + dep);

        // Cache the deployed class.
        Class<?> cls = dep.deployedClass(meta.className(), meta.alias());

        if (cls == null) {
          U.warn(
              log,
              "Failed to load peer class (ignore if class got undeployed during preloading) [alias="
                  + meta.alias()
                  + ", dep="
                  + dep
                  + ']');

          return null;
        }

        return dep;
      }

      assert meta.parentLoader() == null;
      assert depsToCheck != null;
      assert !depsToCheck.isEmpty();

      /*
       * Logic below must be performed outside of synchronization
       * because it involves network calls.
       */

      // Check if class can be loaded from existing nodes.
      // In most cases this loop will find something.
      for (SharedDeployment d : depsToCheck) {
        // Load class. Note, that remote node will not load this class.
        // The class will only be loaded on this node.
        Class<?> cls = d.deployedClass(meta.className(), meta.alias());

        if (cls != null) {
          synchronized (mux) {
            if (!d.isUndeployed() && !d.isPendingUndeploy()) {
              if (!addParticipant(d, meta)) return null;

              if (log.isDebugEnabled())
                log.debug(
                    "Acquired deployment after verifying it's availability on "
                        + "existing nodes [depCls="
                        + cls
                        + ", dep="
                        + d
                        + ", meta="
                        + meta
                        + ']');

              return d;
            }
          }
        } else if (log.isDebugEnabled()) {
          log.debug(
              "Deployment cannot be reused (class does not exist on participating nodes) [dep="
                  + d
                  + ", meta="
                  + meta
                  + ']');
        }
      }

      // We are here either because all participant nodes failed
      // or the class indeed should have a separate deployment.
      for (SharedDeployment d : depsToCheck) {
        // Temporary class loader.
        ClassLoader temp =
            new GridDeploymentClassLoader(
                GridUuid.randomUuid(),
                meta.userVersion(),
                meta.deploymentMode(),
                true,
                ctx,
                ctxLdr,
                meta.classLoaderId(),
                meta.senderNodeId(),
                meta.sequenceNumber(),
                comm,
                ctx.config().getNetworkTimeout(),
                log,
                ctx.config().getPeerClassLoadingClassPathExclude(),
                0,
                false);

        String path = U.classNameToResourceName(d.sampleClassName());

        // We check if any random class from existing deployment can be
        // loaded from sender node. If it can, then we reuse existing
        // deployment.
        InputStream rsrcIn = temp.getResourceAsStream(path);

        if (rsrcIn != null) {
          // We don't need the actual stream.
          U.closeQuiet(rsrcIn);

          synchronized (mux) {
            if (d.isUndeployed() || d.isPendingUndeploy()) continue;

            // Add new node prior to loading the class, so we attempt
            // to load the class from the latest node.
            if (!addParticipant(d, meta)) {
              if (log.isDebugEnabled())
                log.debug(
                    "Failed to add participant to deployment "
                        + "[meta="
                        + meta
                        + ", dep="
                        + dep
                        + ']');

              return null;
            }
          }

          Class<?> depCls = d.deployedClass(meta.className(), meta.alias());

          if (depCls == null) {
            U.error(
                log,
                "Failed to peer load class after loading it as a resource [alias="
                    + meta.alias()
                    + ", dep="
                    + dep
                    + ']');

            return null;
          }

          if (log.isDebugEnabled())
            log.debug(
                "Acquired deployment class after verifying other class "
                    + "availability on sender node [depCls="
                    + depCls
                    + ", rndCls="
                    + d.sampleClass()
                    + ", sampleClsName="
                    + d.sampleClassName()
                    + ", meta="
                    + meta
                    + ']');

          return d;
        } else if (log.isDebugEnabled())
          log.debug(
              "Deployment cannot be reused (random class could not be loaded from sender node) [dep="
                  + d
                  + ", meta="
                  + meta
                  + ']');
      }

      synchronized (mux) {
        if (log.isDebugEnabled())
          log.debug(
              "None of the existing class-loaders fit (will try to create a new one): " + meta);

        // Check obsolete request.
        if (isDeadClassLoader(meta)) return null;

        // Check that deployment picture has not changed.
        List<SharedDeployment> deps = cache.get(meta.userVersion());

        if (deps != null) {
          assert !deps.isEmpty();

          boolean retry = false;

          for (SharedDeployment d : deps) {
            // Double check if sender was already added.
            if (d.hasParticipant(meta.senderNodeId(), meta.classLoaderId())) {
              dep = d;

              retry = false;

              break;
            }

            // New deployment was added while outside of synchronization.
            // Need to recheck it again.
            if (!d.isPendingUndeploy() && !d.isUndeployed() && !depsToCheck.contains(d))
              retry = true;
          }

          if (retry) {
            if (log.isDebugEnabled()) log.debug("Retrying due to concurrency issues: " + meta);

            // Outer while loop.
            continue;
          }

          if (dep == null) {
            // No new deployments were added, so we can safely add ours.
            dep = createNewDeployment(meta, false);

            deps.add(dep);

            if (log.isDebugEnabled())
              log.debug(
                  "Adding new deployment within second check [dep=" + dep + ", meta=" + meta + ']');
          }
        } else {
          dep = createNewDeployment(meta, true);

          if (log.isDebugEnabled())
            log.debug(
                "Created new deployment within second check [dep=" + dep + ", meta=" + meta + ']');
        }
      }

      if (dep != null) {
        // Cache the deployed class.
        Class<?> cls = dep.deployedClass(meta.className(), meta.alias());

        if (cls == null) {
          U.warn(
              log,
              "Failed to load peer class (ignore if class got undeployed during preloading) [alias="
                  + meta.alias()
                  + ", dep="
                  + dep
                  + ']');

          return null;
        }
      }

      return dep;
    }
  }