/** {@inheritDoc} */
  @Override
  public R get(long timeout, TimeUnit unit) throws GridException {
    A.ensure(timeout >= 0, "timeout cannot be negative: " + timeout);
    A.notNull(unit, "unit");

    checkValid();

    try {
      long now = System.currentTimeMillis();

      long end = timeout == 0 ? Long.MAX_VALUE : now + MILLISECONDS.convert(timeout, unit);

      // Account for overflow.
      if (end < 0) end = Long.MAX_VALUE;

      synchronized (mux) {
        while (!done && !cancelled && now < end) {
          mux.wait(end - now);

          if (!done) now = System.currentTimeMillis();
        }

        if (done) {
          if (err != null) throw U.cast(err);

          return res;
        }

        if (cancelled) throw new GridFutureCancelledException("Future was cancelled: " + this);

        throw new GridFutureTimeoutException(
            "Timeout was reached before computation completed [duration="
                + duration()
                + "ms, timeout="
                + unit.toMillis(timeout)
                + "ms]");
      }
    } catch (InterruptedException e) {
      throw new GridInterruptedException(
          "Got interrupted while waiting for future to complete [duration="
              + duration()
              + "ms, timeout="
              + unit.toMillis(timeout)
              + "ms]",
          e);
    }
  }
  /**
   * Checks {@link GridSystemProperties#GG_JETTY_PORT} system property and overrides default
   * connector port if it present. Then initializes {@code port} with the found value.
   *
   * @param con Jetty connector.
   */
  private void override(AbstractNetworkConnector con) {
    String host = System.getProperty(GG_JETTY_HOST);

    if (!F.isEmpty(host)) con.setHost(host);

    int currPort = con.getPort();

    Integer overridePort = Integer.getInteger(GG_JETTY_PORT);

    if (overridePort != null && overridePort != 0) currPort = overridePort;

    con.setPort(currPort);
    port = currPort;
  }
  /**
   * Callback to notify that future is finished. Note that if non-{@code null} exception is passed
   * in the result value will be ignored.
   *
   * @param res Optional result.
   * @param err Optional error.
   * @return {@code True} if result was set by this call.
   */
  public boolean onDone(@Nullable R res, @Nullable Throwable err) {
    checkValid();

    boolean notify = false;

    boolean gotDone = false;

    try {
      synchronized (mux) {
        if (!done) {
          gotDone = true;

          endTime = System.currentTimeMillis();

          this.res = res;
          this.err = err;

          done = true;

          notify = true;

          mux.notifyAll(); // Notify possibly waiting child classes.

          return true;
        }

        return false;
      }
    } finally {
      if (gotDone) {
        GridStopwatch w = watch;

        if (w != null) w.stop();
      }

      if (notify) notifyListeners();
    }
  }
/**
 * Future adapter.
 *
 * @author 2005-2011 Copyright (C) GridGain Systems, Inc.
 * @version 3.1.1c.19062011
 */
public class GridFutureAdapter<R> extends GridMetadataAwareAdapter
    implements GridFuture<R>, Externalizable {
  /** Synchronous notification flag. */
  private static final boolean SYNC_NOTIFY = U.isFutureNotificationSynchronous();

  /** Concurrent notification flag. */
  private static final boolean CONCUR_NOTIFY = U.isFutureNotificationConcurrent();

  /** Done flag. */
  private boolean done;

  /** Cancelled flag. */
  private boolean cancelled;

  /** Result. */
  @GridToStringInclude private R res;

  /** Error. */
  private Throwable err;

  /** Set to {@code false} on deserialization whenever incomplete future is serialized. */
  private boolean valid = true;

  /** Asynchronous listener. */
  private final Set<GridInClosure<? super GridFuture<R>>> lsnrs =
      new GridLeanSet<GridInClosure<? super GridFuture<R>>>();

  /** Creator thread. */
  private Thread thread = Thread.currentThread();

  /** Mutex. */
  private final Object mux = new Object();

  /** Context. */
  protected GridKernalContext ctx;

  /** Logger. */
  protected GridLogger log;

  /** Future start time. */
  protected final long startTime = System.currentTimeMillis();

  /** Synchronous notification flag. */
  private volatile boolean syncNotify = SYNC_NOTIFY;

  /** Concurrent notification flag. */
  private volatile boolean concurNotify = CONCUR_NOTIFY;

  /** Future end time. */
  private volatile long endTime;

  /** Watch. */
  protected GridStopwatch watch;

  /** Empty constructor required for {@link Externalizable}. */
  public GridFutureAdapter() {
    // No-op.
  }

  /** @param ctx Kernal context. */
  public GridFutureAdapter(GridKernalContext ctx) {
    assert ctx != null;

    this.ctx = ctx;

    log = ctx.log(getClass());
  }

  /** {@inheritDoc} */
  @Override
  public long startTime() {
    return startTime;
  }

  /** {@inheritDoc} */
  @Override
  public long duration() {
    long endTime = this.endTime;

    return endTime == 0 ? System.currentTimeMillis() - startTime : endTime - startTime;
  }

  /** {@inheritDoc} */
  @Override
  public boolean concurrentNotify() {
    return concurNotify;
  }

  /** {@inheritDoc} */
  @Override
  public void concurrentNotify(boolean concurNotify) {
    this.concurNotify = concurNotify;
  }

  /** {@inheritDoc} */
  @Override
  public boolean syncNotify() {
    return syncNotify;
  }

  /** {@inheritDoc} */
  @Override
  public void syncNotify(boolean syncNotify) {
    this.syncNotify = syncNotify;
  }

  /**
   * Adds a watch to this future.
   *
   * @param name Name of the watch.
   */
  public void addWatch(String name) {
    assert name != null;

    watch = W.stopwatch(name);
  }

  /**
   * Adds a watch to this future.
   *
   * @param watch Watch to add.
   */
  public void addWatch(GridStopwatch watch) {
    assert watch != null;

    this.watch = watch;
  }

  /** Checks that future is in usable state. */
  protected void checkValid() {
    if (!valid)
      throw new IllegalStateException(
          "Incomplete future was serialized and cannot " + "be used after deserialization.");
  }

  /** @return Valid flag. */
  protected boolean isValid() {
    return valid;
  }

  /**
   * Gets internal mutex.
   *
   * @return Internal mutex.
   */
  protected Object mutex() {
    checkValid();

    return mux;
  }

  /** @return Value of error. */
  protected Throwable error() {
    checkValid();

    synchronized (mux) {
      return err;
    }
  }

  /** @return Value of result. */
  protected R result() {
    checkValid();

    synchronized (mux) {
      return res;
    }
  }

  /** {@inheritDoc} */
  @Override
  public R call() throws Exception {
    return get();
  }

  /** {@inheritDoc} */
  @Override
  public R get(long timeout) throws GridException {
    return get(timeout, MILLISECONDS);
  }

  /** {@inheritDoc} */
  @Override
  public R get() throws GridException {
    checkValid();

    try {
      synchronized (mux) {
        while (!done && !cancelled) mux.wait();

        if (done) {
          if (err != null) throw U.cast(err);

          return res;
        }

        throw new GridFutureCancelledException("Future was cancelled: " + this);
      }
    } catch (InterruptedException e) {
      throw new GridInterruptedException(e);
    }
  }

  /** {@inheritDoc} */
  @Override
  public R get(long timeout, TimeUnit unit) throws GridException {
    A.ensure(timeout >= 0, "timeout cannot be negative: " + timeout);
    A.notNull(unit, "unit");

    checkValid();

    try {
      long now = System.currentTimeMillis();

      long end = timeout == 0 ? Long.MAX_VALUE : now + MILLISECONDS.convert(timeout, unit);

      // Account for overflow.
      if (end < 0) end = Long.MAX_VALUE;

      synchronized (mux) {
        while (!done && !cancelled && now < end) {
          mux.wait(end - now);

          if (!done) now = System.currentTimeMillis();
        }

        if (done) {
          if (err != null) throw U.cast(err);

          return res;
        }

        if (cancelled) throw new GridFutureCancelledException("Future was cancelled: " + this);

        throw new GridFutureTimeoutException(
            "Timeout was reached before computation completed [duration="
                + duration()
                + "ms, timeout="
                + unit.toMillis(timeout)
                + "ms]");
      }
    } catch (InterruptedException e) {
      throw new GridInterruptedException(
          "Got interrupted while waiting for future to complete [duration="
              + duration()
              + "ms, timeout="
              + unit.toMillis(timeout)
              + "ms]",
          e);
    }
  }

  /** {@inheritDoc} */
  @SuppressWarnings({"unchecked"})
  @Override
  public void listenAsync(@Nullable final GridInClosure<? super GridFuture<R>> lsnr) {
    if (lsnr != null) {
      checkValid();

      boolean done;

      synchronized (mux) {
        done = this.done;

        if (!done) lsnrs.add(lsnr);
      }

      if (done) {
        try {
          if (syncNotify) notifyListener(lsnr);
          else
            ctx.closure()
                .runLocalSafe(
                    new GPR() {
                      @Override
                      public void run() {
                        notifyListener(lsnr);
                      }
                    },
                    true);
        } catch (IllegalStateException ignore) {
          U.warn(
              null,
              "Future notification will not proceed because grid is stopped: " + ctx.gridName());
        }
      }
    }
  }

  /** {@inheritDoc} */
  @Override
  public void stopListenAsync(@Nullable GridInClosure<? super GridFuture<R>>... lsnr) {
    if (F.isEmpty(lsnr))
      synchronized (mux) {
        lsnrs.clear();
      }
    else
      synchronized (mux) {
        lsnrs.removeAll(F.asList(lsnr));
      }
  }

  /** Notifies all registered listeners. */
  private void notifyListeners() {
    final Collection<GridInClosure<? super GridFuture<R>>> tmp;

    synchronized (mux) {
      tmp = new ArrayList<GridInClosure<? super GridFuture<R>>>(lsnrs);
    }

    boolean concurNotify = this.concurNotify;
    boolean syncNotify = this.syncNotify;

    if (concurNotify) {
      for (final GridInClosure<? super GridFuture<R>> lsnr : tmp)
        ctx.closure()
            .runLocalSafe(
                new GPR() {
                  @Override
                  public void run() {
                    notifyListener(lsnr);
                  }
                },
                true);
    } else {
      // Always notify in the thread different from start thread.
      if (Thread.currentThread() == thread && !syncNotify) {
        ctx.closure()
            .runLocalSafe(
                new GPR() {
                  @Override
                  public void run() {
                    // Since concurrent notifications are off, we notify
                    // all listeners in one thread.
                    for (GridInClosure<? super GridFuture<R>> lsnr : tmp) notifyListener(lsnr);
                  }
                },
                true);
      } else {
        for (GridInClosure<? super GridFuture<R>> lsnr : tmp) notifyListener(lsnr);
      }
    }
  }

  /**
   * Notifies single listener.
   *
   * @param lsnr Listener.
   */
  private void notifyListener(GridInClosure<? super GridFuture<R>> lsnr) {
    assert lsnr != null;

    try {
      lsnr.apply(this);
    } catch (IllegalStateException ignore) {
      U.warn(
          null,
          "Failed to notify listener (grid is stopped) [grid="
              + ctx.gridName()
              + ", lsnr="
              + lsnr
              + ']');
    } catch (RuntimeException e) {
      U.error(log, "Failed to notify listener: " + lsnr, e);

      throw e;
    } catch (Error e) {
      U.error(log, "Failed to notify listener: " + lsnr, e);

      throw e;
    }
  }

  /**
   * Default no-op implementation that always returns {@code false}. Futures that do support
   * cancellation should override this method and call {@link #onCancelled()} callback explicitly if
   * cancellation indeed did happen.
   */
  @Override
  public boolean cancel() throws GridException {
    checkValid();

    return false;
  }

  /** {@inheritDoc} */
  @Override
  public boolean isDone() {
    // Don't check for "valid" here, as "done" flag can be read
    // even in invalid state.
    synchronized (mux) {
      return done || cancelled;
    }
  }

  /** {@inheritDoc} */
  @Override
  public GridAbsPredicate predicate() {
    return new PA() {
      @Override
      public boolean apply() {
        return isDone();
      }
    };
  }

  /** {@inheritDoc} */
  @Override
  public boolean isCancelled() {
    checkValid();

    synchronized (mux) {
      return cancelled;
    }
  }

  /**
   * Callback to notify that future is finished with {@code null} result. This method must delegate
   * to {@link #onDone(Object, Throwable)} method.
   *
   * @return {@code True} if result was set by this call.
   */
  public final boolean onDone() {
    return onDone(null, null);
  }

  /**
   * Callback to notify that future is finished. This method must delegate to {@link #onDone(Object,
   * Throwable)} method.
   *
   * @param res Result.
   * @return {@code True} if result was set by this call.
   */
  public final boolean onDone(@Nullable R res) {
    return onDone(res, null);
  }

  /**
   * Callback to notify that future is finished. This method must delegate to {@link #onDone(Object,
   * Throwable)} method.
   *
   * @param err Error.
   * @return {@code True} if result was set by this call.
   */
  public final boolean onDone(@Nullable Throwable err) {
    return onDone(null, err);
  }

  /**
   * Callback to notify that future is finished. Note that if non-{@code null} exception is passed
   * in the result value will be ignored.
   *
   * @param res Optional result.
   * @param err Optional error.
   * @return {@code True} if result was set by this call.
   */
  public boolean onDone(@Nullable R res, @Nullable Throwable err) {
    checkValid();

    boolean notify = false;

    boolean gotDone = false;

    try {
      synchronized (mux) {
        if (!done) {
          gotDone = true;

          endTime = System.currentTimeMillis();

          this.res = res;
          this.err = err;

          done = true;

          notify = true;

          mux.notifyAll(); // Notify possibly waiting child classes.

          return true;
        }

        return false;
      }
    } finally {
      if (gotDone) {
        GridStopwatch w = watch;

        if (w != null) w.stop();
      }

      if (notify) notifyListeners();
    }
  }

  /**
   * Callback to notify that future is cancelled.
   *
   * @return {@code True} if cancel flag was set by this call.
   */
  public boolean onCancelled() {
    checkValid();

    synchronized (mux) {
      if (cancelled || done) return false;

      cancelled = true;

      mux.notifyAll(); // Notify possibly waiting child classes.
    }

    return true;
  }

  /** {@inheritDoc} */
  @Override
  public void writeExternal(ObjectOutput out) throws IOException {
    boolean done;
    boolean cancelled;
    Object res;
    Throwable err;
    boolean syncNotify;
    boolean concurNotify;

    synchronized (mux) {
      done = this.done;
      cancelled = this.cancelled;
      res = this.res;
      err = this.err;
      syncNotify = this.syncNotify;
      concurNotify = this.concurNotify;
    }

    out.writeBoolean(done);
    out.writeBoolean(syncNotify);
    out.writeBoolean(concurNotify);

    // Don't write any further if not done, as deserialized future
    // will be invalid anyways.
    if (done) {
      out.writeBoolean(cancelled);
      out.writeObject(res);
      out.writeObject(err);
    }
  }

  /** {@inheritDoc} */
  @SuppressWarnings({"unchecked"})
  @Override
  public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
    boolean done = in.readBoolean();

    syncNotify = in.readBoolean();
    concurNotify = in.readBoolean();

    if (!done) valid = false;
    else {
      boolean cancelled = in.readBoolean();

      R res = (R) in.readObject();

      Throwable err = (Throwable) in.readObject();

      synchronized (mux) {
        this.done = done;
        this.cancelled = cancelled;
        this.res = res;
        this.err = err;
      }
    }
  }

  /** {@inheritDoc} */
  @Override
  public String toString() {
    return S.toString(GridFutureAdapter.class, this);
  }
}
  /** {@inheritDoc} */
  @Override
  public long duration() {
    long endTime = this.endTime;

    return endTime == 0 ? System.currentTimeMillis() - startTime : endTime - startTime;
  }
  /** {@inheritDoc} */
  @SuppressWarnings("BusyWait")
  @Override
  public void start(GridRestProtocolHandler hnd) throws GridException {
    InetAddress locHost;

    try {
      locHost = U.resolveLocalHost(ctx.config().getLocalHost());
    } catch (IOException e) {
      throw new GridException(
          "Failed to resolve local host to bind address: " + ctx.config().getLocalHost(), e);
    }

    System.setProperty(GG_JETTY_HOST, locHost.getHostAddress());

    jettyHnd =
        new GridJettyRestHandler(
            hnd,
            new C1<String, Boolean>() {
              @Override
              public Boolean apply(String tok) {
                return F.isEmpty(secretKey) || authenticate(tok);
              }
            },
            log);

    String jettyPath = ctx.config().getRestJettyPath();

    final URL cfgUrl;

    if (jettyPath == null) {
      cfgUrl = null;

      if (log.isDebugEnabled())
        log.debug("Jetty configuration file is not provided, using defaults.");
    } else {
      cfgUrl = U.resolveGridGainUrl(jettyPath);

      if (cfgUrl == null)
        throw new GridSpiException("Invalid Jetty configuration file: " + jettyPath);
      else if (log.isDebugEnabled()) log.debug("Jetty configuration file: " + cfgUrl);
    }

    loadJettyConfiguration(cfgUrl);

    AbstractNetworkConnector connector = getJettyConnector();

    try {
      host = InetAddress.getByName(connector.getHost());
    } catch (UnknownHostException e) {
      throw new GridException("Failed to resolve Jetty host address: " + connector.getHost(), e);
    }

    int initPort = connector.getPort();

    int lastPort = initPort + ctx.config().getRestPortRange() - 1;

    for (port = initPort; port <= lastPort; port++) {
      connector.setPort(port);

      if (startJetty()) {
        if (log.isInfoEnabled()) log.info(startInfo());

        return;
      }
    }

    U.warn(
        log,
        "Failed to start Jetty REST server (possibly all ports in range are in use) "
            + "[firstPort="
            + initPort
            + ", lastPort="
            + lastPort
            + ']');
  }
  /**
   * Loads jetty configuration from the given URL.
   *
   * @param cfgUrl URL to load configuration from.
   * @throws GridException if load failed.
   */
  private void loadJettyConfiguration(@Nullable URL cfgUrl) throws GridException {
    if (cfgUrl == null) {
      HttpConfiguration httpCfg = new HttpConfiguration();

      httpCfg.setSecureScheme("https");
      httpCfg.setSecurePort(8443);
      httpCfg.setSendServerVersion(true);
      httpCfg.setSendDateHeader(true);

      String srvPortStr = System.getProperty(GG_JETTY_PORT, "8080");

      int srvPort;

      try {
        srvPort = Integer.valueOf(srvPortStr);
      } catch (NumberFormatException ignore) {
        throw new GridException(
            "Failed to start Jetty server because GRIDGAIN_JETTY_PORT system property "
                + "cannot be cast to integer: "
                + srvPortStr);
      }

      httpSrv = new Server(new QueuedThreadPool(20, 200));

      ServerConnector srvConn = new ServerConnector(httpSrv, new HttpConnectionFactory(httpCfg));

      srvConn.setHost(System.getProperty(GG_JETTY_HOST, "localhost"));
      srvConn.setPort(srvPort);
      srvConn.setIdleTimeout(30000L);
      srvConn.setReuseAddress(true);

      httpSrv.addConnector(srvConn);

      httpSrv.setStopAtShutdown(false);
    } else {
      XmlConfiguration cfg;

      try {
        cfg = new XmlConfiguration(cfgUrl);
      } catch (FileNotFoundException e) {
        throw new GridSpiException("Failed to find configuration file: " + cfgUrl, e);
      } catch (SAXException e) {
        throw new GridSpiException("Failed to parse configuration file: " + cfgUrl, e);
      } catch (IOException e) {
        throw new GridSpiException("Failed to load configuration file: " + cfgUrl, e);
      } catch (Exception e) {
        throw new GridSpiException(
            "Failed to start HTTP server with configuration file: " + cfgUrl, e);
      }

      try {
        httpSrv = (Server) cfg.configure();
      } catch (Exception e) {
        throw new GridException("Failed to start Jetty HTTP server.", e);
      }
    }

    assert httpSrv != null;

    httpSrv.setHandler(jettyHnd);

    override(getJettyConnector());
  }
  /**
   * 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);
  }