Package org.cloudfoundry.ide.eclipse.server.core.internal.client

Source Code of org.cloudfoundry.ide.eclipse.server.core.internal.client.CloudFoundryServerBehaviour$ApplicationOperation

/*******************************************************************************
* Copyright (c) 2012, 2014 Pivotal Software, Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of 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.
*  Contributors:
*     Pivotal Software, Inc. - initial API and implementation
*     IBM - wait for all module publish complete before finish up publish operation.
********************************************************************************/
package org.cloudfoundry.ide.eclipse.server.core.internal.client;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.cloudfoundry.client.lib.ApplicationLogListener;
import org.cloudfoundry.client.lib.CloudCredentials;
import org.cloudfoundry.client.lib.CloudFoundryException;
import org.cloudfoundry.client.lib.CloudFoundryOperations;
import org.cloudfoundry.client.lib.NotFinishedStagingException;
import org.cloudfoundry.client.lib.StartingInfo;
import org.cloudfoundry.client.lib.StreamingLogToken;
import org.cloudfoundry.client.lib.UploadStatusCallback;
import org.cloudfoundry.client.lib.archive.ApplicationArchive;
import org.cloudfoundry.client.lib.domain.ApplicationLog;
import org.cloudfoundry.client.lib.domain.ApplicationStats;
import org.cloudfoundry.client.lib.domain.CloudApplication;
import org.cloudfoundry.client.lib.domain.CloudApplication.AppState;
import org.cloudfoundry.client.lib.domain.CloudDomain;
import org.cloudfoundry.client.lib.domain.CloudRoute;
import org.cloudfoundry.client.lib.domain.CloudService;
import org.cloudfoundry.client.lib.domain.CloudServiceOffering;
import org.cloudfoundry.client.lib.domain.CloudSpace;
import org.cloudfoundry.client.lib.domain.InstancesInfo;
import org.cloudfoundry.client.lib.domain.Staging;
import org.cloudfoundry.ide.eclipse.server.core.AbstractAppStateTracker;
import org.cloudfoundry.ide.eclipse.server.core.AbstractApplicationDelegate;
import org.cloudfoundry.ide.eclipse.server.core.ApplicationDeploymentInfo;
import org.cloudfoundry.ide.eclipse.server.core.ICloudFoundryApplicationModule;
import org.cloudfoundry.ide.eclipse.server.core.internal.ApplicationAction;
import org.cloudfoundry.ide.eclipse.server.core.internal.ApplicationUrlLookupService;
import org.cloudfoundry.ide.eclipse.server.core.internal.CachingApplicationArchive;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudErrorUtil;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudFoundryLoginHandler;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudFoundryPlugin;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudFoundryServer;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudUtil;
import org.cloudfoundry.ide.eclipse.server.core.internal.Messages;
import org.cloudfoundry.ide.eclipse.server.core.internal.ModuleResourceDeltaWrapper;
import org.cloudfoundry.ide.eclipse.server.core.internal.RefreshHandler;
import org.cloudfoundry.ide.eclipse.server.core.internal.ServerEventHandler;
import org.cloudfoundry.ide.eclipse.server.core.internal.application.ApplicationRegistry;
import org.cloudfoundry.ide.eclipse.server.core.internal.application.EnvironmentVariable;
import org.cloudfoundry.ide.eclipse.server.core.internal.debug.CloudFoundryProperties;
import org.cloudfoundry.ide.eclipse.server.core.internal.debug.DebugModeType;
import org.cloudfoundry.ide.eclipse.server.core.internal.spaces.CloudFoundrySpace;
import org.cloudfoundry.ide.eclipse.server.core.internal.spaces.CloudOrgsAndSpaces;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jst.server.core.IWebModule;
import org.eclipse.osgi.util.NLS;
import org.eclipse.wst.server.core.IModule;
import org.eclipse.wst.server.core.IServer;
import org.eclipse.wst.server.core.IServerListener;
import org.eclipse.wst.server.core.ServerEvent;
import org.eclipse.wst.server.core.internal.Server;
import org.eclipse.wst.server.core.model.IModuleFile;
import org.eclipse.wst.server.core.model.IModuleResource;
import org.eclipse.wst.server.core.model.IModuleResourceDelta;
import org.eclipse.wst.server.core.model.ServerBehaviourDelegate;
import org.springframework.web.client.RestClientException;

/**
*
* Contains many of the calls to the CF Java client. The CF server behaviour
* should be the main call point for interacting with the actual cloud server,
* with the exception of Caldecott, which is handled in a similar behaviour.
* <p/>
* It's important to note that almost all Java client calls are wrapped around a
* Request object, and it is important to wrap future client calls around a
* Request object, as the request object handles automatic client login, server
* state verification, and proxy handling.
*
* <p/>
* It is important to note that Application operations like deploying, starting,
* restarting, update restarting, and stopping should be performed atomically as
* {@link ApplicationOperation}, as the operation , among other things:
* <p/>
* 1. Ensures the deployment information for the application is complete and
* valid before performing the operation.
* <p/>
* 2. Ensures any active refresh jobs running in the background are stopped
* while the operation is performed
* <p/>
* 3. Ensures any active stopped refresh jobs are restarted after the operation.
* <p/>
* 4. Handles any common errors associated with these operations, in particular
* staging errors.
* <p/>
* WST framework publishing of applications (e.g. drag/drop to Servers view, or
* Run On Server), rely on
* {@link #publishModule(int, int, IModule[], IProgressMonitor)}. It's important
* to note that as of CF 1.6.1, all WST framework-based publishings will result
* in server-level publishing, so even if deploying a particular application,
* other applications that are already deployed and not external (i.e. have a
* corresponding workspace project) that need republishing may be republished as
* well.
*
* IMPORTANT NOTE: This class can be referred by the branding extension from
* adopter so this class should not be moved or renamed to avoid breakage to
* adopters.
*
* @author Christian Dupuis
* @author Leo Dos Santos
* @author Terry Denney
* @author Steffen Pingel
* @author Nieraj Singh
*/
@SuppressWarnings("restriction")
public class CloudFoundryServerBehaviour extends ServerBehaviourDelegate {

  private CloudFoundryOperations client;

  private RefreshHandler refreshHandler;

  private ApplicationUrlLookupService applicationUrlLookup;

  public static String INTERNAL_ERROR_NO_WST_MODULE = "Internal Error: No WST IModule specified - Unable to deploy or start application"; //$NON-NLS-1$

  public static String INTERNAL_ERROR_NO_MAPPED_CLOUD_MODULE = "Internal Error: No mapped application module found for: {0} - Unable to deploy or start application"; //$NON-NLS-1$

  /*
   * FIXNS: Until V2 MCF is released, disable debugging support for V2, as
   * public clouds also indicate they support debug.
   */
  private DebugSupportCheck isDebugModeSupported = DebugSupportCheck.UNSUPPORTED;

  private IServerListener serverListener = new IServerListener() {

    public void serverChanged(ServerEvent event) {
      if (event.getKind() == ServerEvent.SERVER_CHANGE) {
        // reset client to consume updated credentials at a later stage.
        // Do not connect
        // right away
        //
        internalResetClient();
      }
    }
  };

  protected enum DebugSupportCheck {
    // Initial state of the debug support check. used so that further checks
    // are not necessary in a given session
    UNCHECKED,
    // Server supports debug mode
    SUPPORTED,
    // Server does not support debug mode
    UNSUPPORTED,
  }

  @Override
  public boolean canControlModule(IModule[] module) {
    return module.length == 1;
  }

  public void connect(IProgressMonitor monitor) throws CoreException {
    final CloudFoundryServer cloudServer = getCloudFoundryServer();

    new BehaviourRequest<Void>("Loggging in to " + cloudServer.getUrl()) { //$NON-NLS-1$
      @Override
      protected Void doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
        client.login();
        return null;
      }
    }.run(monitor);

    Server server = (Server) cloudServer.getServerOriginal();
    server.setServerState(IServer.STATE_STARTED);
    server.setServerPublishState(IServer.PUBLISH_STATE_NONE);

    getApplicationUrlLookup().refreshDomains(monitor);

    refreshModules(monitor);
  }

  /**
   * Determine if server supports debug mode, if necessary by sending a
   * request to the server. The information is cached for quicker, subsequent
   * checks.
   *
   */
  protected synchronized void requestAllowDebug(CloudFoundryOperations client) throws CoreException {
    // Check the debug support of the server once per working copy of server
    if (isDebugModeSupported == DebugSupportCheck.UNCHECKED) {
      isDebugModeSupported = client.getCloudInfo().getAllowDebug() ? DebugSupportCheck.SUPPORTED
          : DebugSupportCheck.UNSUPPORTED;
    }
  }

  public RefreshHandler getRefreshHandler() {
    return refreshHandler;
  }

  /**
   * Creates the given list of services
   * @param services
   * @param monitor
   * @throws CoreException
   */
  public void createService(final CloudService[] services, IProgressMonitor monitor) throws CoreException {

    new BehaviourRequest<Void>(services.length == 1 ? "Creating service " + services[0].getName() //$NON-NLS-1$
    : "Creating services") { //$NON-NLS-1$
      @Override
      protected Void doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {

        for (CloudService service : services) {
          client.createService(service);
        }

        return null;
      }
    }.run(monitor);
    ServerEventHandler.getDefault().fireServicesUpdated(getCloudFoundryServer());
  }

  public synchronized List<CloudDomain> getDomainsFromOrgs(IProgressMonitor monitor) throws CoreException {
    return new BehaviourRequest<List<CloudDomain>>("Getting domains for orgs") { //$NON-NLS-1$
      @Override
      protected List<CloudDomain> doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
        return client.getDomainsForOrg();
      }
    }.run(monitor);

  }

  public synchronized List<CloudDomain> getDomainsForSpace(IProgressMonitor monitor) throws CoreException {

    return new BehaviourRequest<List<CloudDomain>>("Getting domains for current space") { //$NON-NLS-1$
      @Override
      protected List<CloudDomain> doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
        return client.getDomains();
      }
    }.run(monitor);
  }

  /**
   * Deletes the given modules. Note that any refresh job is stopped while
   * this operation is running, and restarted after its complete.
   * @param modules
   * @param deleteServices
   * @param monitor
   * @throws CoreException
   */
  public void deleteModules(final IModule[] modules, final boolean deleteServices, IProgressMonitor monitor)
      throws CoreException {
    new DeleteModulesOperation(this, modules, deleteServices, this).run(monitor);
  }

  public void deleteApplication(String appName, IProgressMonitor monitor) throws CoreException {
    final String applicationName = appName;
    new BehaviourRequest<Void>("Deleting applications") { //$NON-NLS-1$
      @Override
      protected Void doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
        client.deleteApplication(applicationName);

        return null;
      }
    }.run(monitor);

  }

  /**
   * Deletes the list of services.
   * @param services
   * @throws CoreException if error occurred during service deletion.
   */
  public ICloudFoundryOperation getDeleteServicesOperation(final List<String> services) throws CoreException {
    return new ModifyOperation(CloudFoundryServerBehaviour.this) {

      @Override
      protected void performOperation(IProgressMonitor monitor) throws CoreException {
        new BehaviourRequest<Void>("Deleting services") { //$NON-NLS-1$
          @Override
          protected Void doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
            TunnelBehaviour handler = new TunnelBehaviour(getCloudFoundryServer());
            for (String service : services) {
              client.deleteService(service);

              // Also delete any existing Tunnels
              handler.stopAndDeleteCaldecottTunnel(service, progress);
            }
            return null;
          }
        }.run(monitor);
      }
    };
  }

  /**
   * The Cloud application URL lookup is used to resolve a list of URL domains
   * that an application can user when specifying a URL.
   * <p/>
   * Note that this only returns a cached lookup. The lookup may have to be
   * refreshed separately to get the most recent list of domains.
   * @return Lookup to retrieve list of application URL domains, as well as
   * verify validity of an application URL. May be null as its a cached
   * version.
   */
  public ApplicationUrlLookupService getApplicationUrlLookup() {
    if (applicationUrlLookup == null) {
      try {
        applicationUrlLookup = new ApplicationUrlLookupService(getCloudFoundryServer());
      }
      catch (CoreException e) {
        CloudFoundryPlugin.logError(
            "Failed to create the Cloud Foundry Application URL lookup service due to {" + //$NON-NLS-1$
                e.getMessage(), e);
      }
    }
    return applicationUrlLookup;
  }

  protected List<IModuleResource> getChangedResources(IModuleResourceDelta[] deltas) {
    List<IModuleResource> changed = new ArrayList<IModuleResource>();
    if (deltas != null) {
      findNonChangedResources(deltas, changed);
    }
    return changed;

  }

  protected void findNonChangedResources(IModuleResourceDelta[] deltas, List<IModuleResource> changed) {
    if (deltas == null || deltas.length == 0) {
      return;
    }
    for (IModuleResourceDelta delta : deltas) {
      // Only handle file resources
      IModuleResource resource = delta.getModuleResource();
      if (resource instanceof IModuleFile && delta.getKind() != IModuleResourceDelta.NO_CHANGE) {
        changed.add(new ModuleResourceDeltaWrapper(delta));
      }

      findNonChangedResources(delta.getAffectedChildren(), changed);
    }
  }

  /**
   * Disconnects the local server from the remote CF server, and terminate the
   * session. Note that this will stop any refresh operations, or console
   * streaming, but will NOT stop any apps that are currently running. It may
   * also clear any application module caches associated with the session.
   * @param monitor
   * @throws CoreException
   */
  public void disconnect(IProgressMonitor monitor) throws CoreException {
    CloudFoundryPlugin.getCallback().disconnecting(getCloudFoundryServer());

    Server server = (Server) getServer();
    server.setServerState(IServer.STATE_STOPPING);

    getRefreshHandler().stop();
    CloudFoundryServer cloudServer = getCloudFoundryServer();

    Collection<CloudFoundryApplicationModule> cloudModules = cloudServer.getExistingCloudModules();

    for (CloudFoundryApplicationModule appModule : cloudModules) {
      CloudFoundryPlugin.getCallback().stopApplicationConsole(appModule, cloudServer);
    }

    Set<CloudFoundryApplicationModule> deletedModules = new HashSet<CloudFoundryApplicationModule>(cloudModules);

    cloudServer.clearApplications();

    // update state for cloud applications
    server.setExternalModules(new IModule[0]);
    for (CloudFoundryApplicationModule module : deletedModules) {
      server.setModuleState(new IModule[] { module.getLocalModule() }, IServer.STATE_UNKNOWN);
    }

    server.setServerState(IServer.STATE_STOPPED);
    server.setServerPublishState(IServer.PUBLISH_STATE_NONE);
    closeCaldecottTunnels(monitor);
  }

  @Override
  public void dispose() {
    super.dispose();
    getServer().removeServerListener(serverListener);
    closeCaldecottTunnelsAsynch();
  }

  /**
   * This method is API used by CloudFoundry Code.
   */
  public CloudFoundryServer getCloudFoundryServer() throws CoreException {
    Server server = (Server) getServer();

    CloudFoundryServer cloudFoundryServer = (CloudFoundryServer) server.loadAdapter(CloudFoundryServer.class, null);
    if (cloudFoundryServer == null) {
      throw new CoreException(new Status(IStatus.ERROR, CloudFoundryPlugin.PLUGIN_ID, "Fail to load server")); //$NON-NLS-1$
    }
    return cloudFoundryServer;
  }

  public CloudApplication getApplication(final String applicationId, IProgressMonitor monitor) throws CoreException {
    return new BehaviourRequest<CloudApplication>("Getting Application " + applicationId) { //$NON-NLS-1$
      @Override
      protected CloudApplication doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
        return client.getApplication(applicationId);
      }
    }.run(monitor);
  }

  public List<CloudApplication> getApplications(IProgressMonitor monitor) throws CoreException {
    return new BehaviourRequest<List<CloudApplication>>("Getting applications") { //$NON-NLS-1$
      @Override
      protected List<CloudApplication> doRun(CloudFoundryOperations client, SubMonitor progress)
          throws CoreException {
        return client.getApplications();
      }
    }.run(monitor);
  }

  public ApplicationStats getApplicationStats(final String applicationId, IProgressMonitor monitor)
      throws CoreException {
    return new StagingAwareRequest<ApplicationStats>("Getting application statistics for " + //$NON-NLS-1$
        applicationId) {
      @Override
      protected ApplicationStats doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
        return client.getApplicationStats(applicationId);
      }
    }.run(monitor);
  }

  public InstancesInfo getInstancesInfo(final String applicationId, IProgressMonitor monitor) throws CoreException {
    return new StagingAwareRequest<InstancesInfo>("Getting application statistics for " + applicationId) { //$NON-NLS-1$
      @Override
      protected InstancesInfo doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
        return client.getApplicationInstances(applicationId);
      }
    }.run(monitor);
  }

  public String getFile(final String applicationId, final int instanceIndex, final String path,
      IProgressMonitor monitor) throws CoreException {
    return new FileRequest<String>() {
      @Override
      protected String doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
        return client.getFile(applicationId, instanceIndex, path);
      }
    }.run(monitor);
  }

  public String getFile(final String applicationId, final int instanceIndex, final String filePath,
      final int startPosition, IProgressMonitor monitor) throws CoreException {
    return new FileRequest<String>() {
      @Override
      protected String doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
        return client.getFile(applicationId, instanceIndex, filePath, startPosition);
      }
    }.run(monitor);
  }

  public List<CloudServiceOffering> getServiceOfferings(IProgressMonitor monitor) throws CoreException {
    return new BehaviourRequest<List<CloudServiceOffering>>("Getting available service options") { //$NON-NLS-1$
      @Override
      protected List<CloudServiceOffering> doRun(CloudFoundryOperations client, SubMonitor progress)
          throws CoreException {
        return client.getServiceOfferings();
      }
    }.run(monitor);
  }

  /**
   * For testing only.
   */
  public void deleteAllApplications(IProgressMonitor monitor) throws CoreException {
    new BehaviourRequest<Object>("Deleting all applications") { //$NON-NLS-1$
      @Override
      protected Object doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
        client.deleteAllApplications();
        return null;
      }
    }.run(monitor);
  }

  public List<CloudService> getServices(IProgressMonitor monitor) throws CoreException {
    return new BehaviourRequest<List<CloudService>>("Getting available services") { //$NON-NLS-1$
      @Override
      protected List<CloudService> doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
        return client.getServices();
      }
    }.run(monitor);
  }

  /**
   * Refresh the application modules and reschedules the app module refresh
   * job to execute at certain intervals. This will synch all local
   * application modules with the actual deployed applications. This may be a
   * long running operation.
   * @param monitor
   */
  public void refreshModules(IProgressMonitor monitor) {
    try {
      final CloudFoundryServer cloudServer = getCloudFoundryServer();

      // Get updated list of cloud applications from the server
      List<CloudApplication> applications = getApplications(monitor);

      // update applications and deployments from server
      Map<String, CloudApplication> deployedApplicationsByName = new LinkedHashMap<String, CloudApplication>();

      for (CloudApplication application : applications) {
        deployedApplicationsByName.put(application.getName(), application);
      }

      cloudServer.updateModules(deployedApplicationsByName);
    }
    catch (Throwable t) {
      // refresh operations MUST not block any other operation.
      // therefore catch all errors and log them
      CloudFoundryPlugin.logError(NLS.bind(Messages.ERROR_FAILED_MODULE_REFRESH, t.getMessage()));
    }
  }

  /**
   * Resets the client. Note that any cached information used by the previous
   * client will be cleared. Credentials used to reset the client will be
   * retrieved from the the local server store.
   * @param monitor
   * @throws CoreException failure to reset client, disconnect using current
   * client, or login/connect to the server using new client
   */
  public CloudFoundryOperations resetClient(IProgressMonitor monitor) throws CoreException {
    return resetClient(null, monitor);
  }

  /**
   * Public for testing only. Clients should not call outside of test
   * framework.Use {@link #resetClient(IProgressMonitor)} for actual client
   * reset, as credentials should not be normally be passed through this API.
   * Credentials typically are stored and retrieved indirectly by the
   * behaviour through the server instance.
   *
   * @param monitor
   * @param credentials
   * @throws CoreException
   */
  public CloudFoundryOperations resetClient(CloudCredentials credentials, IProgressMonitor monitor)
      throws CoreException {
    internalResetClient();
    return getClient(credentials, monitor);
  }

  protected void internalResetClient() {
    client = null;
    applicationUrlLookup = null;
  }

  /**
   * Starts an application in debug mode. Should ONLY be called if the
   * application is currently stopped. Otherwise use
   * {@link #updateRestartDebugModule(IModule[], boolean, IProgressMonitor)}.
   * @param modules
   * @param monitor
   * @return
   * @throws CoreException
   */
  public ICloudFoundryApplicationModule debugModule(IModule[] modules, IProgressMonitor monitor) throws CoreException {
    return doDebugModule(false, modules, false, monitor);
  }

  /**
   * Deploys or starts an app in debug mode and either a full publish or
   * incremental publish may be specified. If incremental publish, changes in
   * the app are automatically computed and only those changes are pushed to
   * the server.
   * @param modules
   * @param fullPublish
   * @param monitor
   * @return
   * @throws CoreException
   */
  protected ICloudFoundryApplicationModule doDebugModule(boolean incrementalPublish, IModule[] modules,
      final boolean stopModule, IProgressMonitor monitor) throws CoreException {

    ApplicationOperation op = new StartOperation(incrementalPublish, modules) {

      @Override
      protected DeploymentConfiguration prepareForDeployment(CloudFoundryApplicationModule appModule,
          IProgressMonitor monitor) throws CoreException {

        if (stopModule) {
          stopModule(modules, monitor);
        }

        return super.prepareForDeployment(appModule, monitor);
      }

      @Override
      protected DeploymentConfiguration getDefaultDeploymentConfiguration() {
        return new DeploymentConfiguration(ApplicationAction.DEBUG);
      }
    };
    op.run(monitor);
    return op.getApplicationModule();

  }

  /**
   * Get an operation to deploy an application in start mode.
   * @param modules
   * @return
   * @throws CoreException
   */
  protected ApplicationOperation internalGetDeployStartApplicationOperation(IModule[] modules) throws CoreException {
    // Start operations always perform a full publish
    boolean incrementalPublish = false;
    return getDeployApplicationOperation(incrementalPublish, modules);
  }

  /**
   * Deploys or starts a module by doing either a full publish or incremental.
   * @param isIncremental true if incremental publish should be attempted.
   * False otherwise
   * @param modules
   * @return {@link ICloudFoundryOperation} that performs the operation when
   * run.
   * @throws CoreException
   */
  protected ApplicationOperation getDeployApplicationOperation(boolean isIncremental, final IModule[] modules)
      throws CoreException {
    return new StartOperation(isIncremental, modules);
  }

  /**
   * Returns non-null Cloud application module mapped to the first module in
   * the list of modules. If the cloud module module does not exist for the
   * given module, it will attempt to create it. To avoid re-creating a cloud
   * application module that may have been deleted, restrict invoking this
   * method to only operations that start, restart, or update an application.
   * Should not be called when deleting an application.
   * @param local WST modules representing app to be deployed.
   * @return non-null Cloud Application module mapped to the given WST module.
   * @throws CoreException if no modules specified or mapped cloud application
   * module cannot be resolved.
   */
  protected CloudFoundryApplicationModule getOrCreateCloudApplicationModule(IModule[] modules) throws CoreException {

    CloudFoundryApplicationModule appModule = null;
    if (modules == null || modules.length == 0) {
      throw CloudErrorUtil.toCoreException(INTERNAL_ERROR_NO_WST_MODULE);
    }
    else {
      IModule module = modules[0];

      CloudFoundryServer cloudServer = getCloudFoundryServer();

      appModule = cloudServer.getCloudModule(module);

      if (appModule == null) {
        throw CloudErrorUtil
            .toCoreException(NLS.bind(INTERNAL_ERROR_NO_MAPPED_CLOUD_MODULE, modules[0].getId()));

      }

    }

    return appModule;
  }

  @Override
  public void startModule(IModule[] modules, IProgressMonitor monitor) throws CoreException {
    ICloudFoundryOperation operation = internalGetDeployStartApplicationOperation(modules);
    operation.run(monitor);
  }

  @Override
  public void stop(boolean force) {
    // This stops the server locally, it does NOT stop the remotely running
    // applications
    setServerState(IServer.STATE_STOPPED);
    closeCaldecottTunnelsAsynch();
  }

  protected void closeCaldecottTunnelsAsynch() {
    String jobName = "Stopping all tunnels"; //$NON-NLS-1$

    try {
      jobName += ": " + getCloudFoundryServer().getDeploymentName(); //$NON-NLS-1$
    }
    catch (CoreException e1) {
      CloudFoundryPlugin.log(e1);
    }

    Job job = new Job(jobName) {

      @Override
      protected IStatus run(IProgressMonitor monitor) {
        closeCaldecottTunnels(monitor);
        return Status.OK_STATUS;
      }

    };
    job.setSystem(false);
    job.schedule();
  }

  protected void closeCaldecottTunnels(IProgressMonitor monitor) {
    // Close all open Caldecott Tunnels
    try {
      new TunnelBehaviour(getCloudFoundryServer()).stopAndDeleteAllTunnels(monitor);
    }
    catch (CoreException e) {
      CloudFoundryPlugin.log(e);
    }
  }

  @Override
  public void stopModule(IModule[] modules, IProgressMonitor monitor) throws CoreException {
    getStopAppOperation(modules).run(monitor);
  }

  public ICloudFoundryOperation getStopAppOperation(IModule[] modules) {
    return new StopApplicationOperation(modules);
  }

  /**
   * Updates and restarts an application in debug mode. Incremental publish
   * will occur on update restarts if any changes are detected.
   * @param modules
   * @param monitor
   * @param isIncrementalPublishing true if optimised incremental publishing
   * should be enabled. False otherwise
   * @return
   * @throws CoreException
   */
  public ICloudFoundryApplicationModule updateRestartDebugModule(IModule[] modules, boolean isIncrementalPublishing,
      IProgressMonitor monitor) throws CoreException {
    return doDebugModule(true, modules, isIncrementalPublishing, monitor);
  }

  public StreamingLogToken addApplicationLogListener(final String appName, final ApplicationLogListener listener) {
    if (appName != null && listener != null) {
      try {
        return new BehaviourRequest<StreamingLogToken>("Adding application log listener") //$NON-NLS-1$
        {
          @Override
          protected StreamingLogToken doRun(CloudFoundryOperations client, SubMonitor progress)
              throws CoreException {
            return client.streamLogs(appName, listener);
          }

        }.run(new NullProgressMonitor());
      }
      catch (CoreException e) {
        CloudFoundryPlugin.logError(NLS.bind(Messages.ERROR_APPLICATION_LOG_LISTENER, appName, e.getMessage()),
            e);
      }
    }

    return null;
  }

  public List<ApplicationLog> getRecentApplicationLogs(final String appName, IProgressMonitor monitor)
      throws CoreException {
    List<ApplicationLog> logs = null;
    if (appName != null) {
      logs = new BehaviourRequest<List<ApplicationLog>>("Getting existing application logs for: " + appName) //$NON-NLS-1$
      {

        @Override
        protected List<ApplicationLog> doRun(CloudFoundryOperations client, SubMonitor progress)
            throws CoreException {
          return client.getRecentLogs(appName);
        }

      }.run(monitor);
    }
    if (logs == null) {
      logs = Collections.emptyList();
    }
    return logs;
  }

  @Override
  /**
   * Note that this automatically restarts a module in the start mode it is currently, or was currently running in.
   * It automatically detects if an application is running in debug mode or regular run mode, and restarts it in that
   * same mode. Other API exists to restart an application in a specific mode, if automatic detection and restart in
   * existing mode is not required.
   */
  public void restartModule(IModule[] modules, IProgressMonitor monitor) throws CoreException {

    if (CloudFoundryProperties.isApplicationRunningInDebugMode.testProperty(modules, getCloudFoundryServer())) {
      restartDebugModule(modules, monitor);
    }
    else {
      ICloudFoundryOperation operation = getRestartOperation(modules);
      operation.run(monitor);
    }

  }

  /**
   * This will restart an application in debug mode only. Does not push
   * application changes or create an application. Application must exist in
   * the CF server if using this operation.
   * @param modules
   * @param monitor
   * @throws CoreException
   */
  public void restartDebugModule(IModule[] modules, IProgressMonitor monitor) throws CoreException {

    new RestartOperation(modules) {

      @Override
      protected DeploymentConfiguration prepareForDeployment(CloudFoundryApplicationModule appModule,
          IProgressMonitor monitor) throws CoreException {
        // Explicitly stop the module to ensure any existing debugger
        // connections are terminated prior to starting a new connection
        stopModule(modules, monitor);

        return super.prepareForDeployment(appModule, monitor);
      }

      @Override
      protected DeploymentConfiguration getDefaultDeploymentConfiguration() {
        return new DeploymentConfiguration(ApplicationAction.DEBUG);
      }
    }.run(monitor);
  }

  /**
   * Update restart republishes redeploys the application with changes. This
   * is not the same as restarting an application which simply restarts the
   * application in its current server version without receiving any local
   * changes. It will only update restart an application in regular run mode.
   * It does not support debug mode.Publishing of changes is done
   * incrementally.
   * @param module to update
   *
   * @throws CoreException
   */
  public ICloudFoundryOperation getUpdateRestartOperation(IModule[] modules) throws CoreException {
    return getDeployApplicationOperation(CloudFoundryPlugin.getDefault().getIncrementalPublish(), modules);
  }

  /**
   * This will restart an application in run mode. It does not restart an
   * application in debug mode. Does not push application resources or create
   * the application. The application must exist in the CloudFoundry server.
   * @param modules
   * @throws CoreException
   */
  public ICloudFoundryOperation getRestartOperation(IModule[] modules) throws CoreException {
    return new RestartOperation(modules);
  }

  /**
   * Updates an the number of application instances. Does not restart the
   * application if the application is already running. The CF server does
   * allow instance scaling to occur while the application is running.
   * @param module representing the application. must not be null or empty
   * @param instanceCount must be 1 or higher.
   * @param monitor
   * @throws CoreException if error occurred during or after instances are
   * updated.
   */
  public void updateApplicationInstances(final CloudFoundryApplicationModule module, final int instanceCount,
      IProgressMonitor monitor) throws CoreException {
    final String appName = module.getApplication().getName();
    new AppInStoppedStateAwareRequest<Void>("Updating application instances") { //$NON-NLS-1$
      @Override
      protected Void doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
        client.updateApplicationInstances(appName, instanceCount);
        return null;
      }
    }.run(monitor);

    ServerEventHandler.getDefault().fireInstancesUpdated(getCloudFoundryServer());
  }

  public void updatePassword(final String newPassword, IProgressMonitor monitor) throws CoreException {
    new BehaviourRequest<Void>("Updating password") { //$NON-NLS-1$

      @Override
      protected Void doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
        client.updatePassword(newPassword);
        return null;
      }

    }.run(monitor);
  }

  /**
   * Updates an application's memory. Does not restart an application if the
   * application is currently running. The CF server does allow memory scaling
   * to occur while the application is running.
   * @param module must not be null or empty
   * @param memory must be above zero.
   * @param monitor
   * @throws CoreException if error occurred during or after memory is scaled.
   * Exception does not always mean that the memory changes did not take
   * effect. Memory could have changed, but some post operation like
   * refreshing may have failed.
   */
  public void updateApplicationMemory(final CloudFoundryApplicationModule module, final int memory,
      IProgressMonitor monitor) throws CoreException {
    final String appName = module.getApplication().getName();
    new AppInStoppedStateAwareRequest<Void>("Updating application memory for " + //$NON-NLS-1$
        module.getDeployedApplicationName()) {
      @Override
      protected Void doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
        client.updateApplicationMemory(appName, memory);
        return null;
      }
    }.run(monitor);
  }

  public void updateApplicationUrls(final String appName, final List<String> uris, IProgressMonitor monitor)
      throws CoreException {

    new UpdateApplicationOperation(appName, this) {
      @Override
      protected void performOperation(IProgressMonitor monitor) throws CoreException {
        new AppInStoppedStateAwareRequest<Void>("Updating application URLs for " + appName) { //$NON-NLS-1$
          @Override
          protected Void doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
            client.updateApplicationUris(appName, uris);
            return null;
          }
        }.run(monitor);
      }
    }.run(monitor);
  }

  public List<String> findCaldecottTunnelsToClose(CloudFoundryOperations client, String appName,
      List<String> servicesToUpdate) {
    List<String> services = new ArrayList<String>();

    CloudApplication caldecottApp = client.getApplication(appName);
    if (caldecottApp != null) {
      List<String> existingServices = caldecottApp.getServices();
      if (existingServices != null) {
        Set<String> possibleDeletedServices = new HashSet<String>();
        // Must iterate rather than passing to constructor or using
        // addAll, as some
        // of the entries in existing services may be null
        for (String existingService : existingServices) {
          if (existingService != null) {
            possibleDeletedServices.add(existingService);
          }
        }

        for (String updatedService : servicesToUpdate) {
          if (possibleDeletedServices.contains(updatedService)) {
            possibleDeletedServices.remove(updatedService);
          }
        }
        services.addAll(possibleDeletedServices);
      }
    }
    return services;
  }

  public void updateServices(String appName, List<String> services, IProgressMonitor monitor) throws CoreException {
    updateServices(appName, services, false, monitor);
  }

  public void refreshApplicationBoundServices(CloudFoundryApplicationModule appModule, IProgressMonitor monitor)
      throws CoreException {
    DeploymentInfoWorkingCopy copy = appModule.resolveDeploymentInfoWorkingCopy(monitor);
    List<CloudService> boundServices = copy.getServices();
    if (boundServices != null && !boundServices.isEmpty()) {

      List<CloudService> allServices = getServices(monitor);
      if (allServices != null) {
        Map<String, CloudService> existingAsMap = new HashMap<String, CloudService>();

        for (CloudService existingServices : allServices) {
          existingAsMap.put(existingServices.getName(), existingServices);
        }

        List<CloudService> updatedServices = new ArrayList<CloudService>();

        for (CloudService boundService : boundServices) {
          CloudService updatedService = existingAsMap.get(boundService.getName());
          // Check if there is an updated mapping to an actual Cloud
          // Service or retain the old one.
          if (updatedService != null) {
            updatedServices.add(updatedService);
          }
          else {
            updatedServices.add(boundService);
          }
        }

        copy.setServices(updatedServices);
        copy.save();
      }

    }
  }

  public void updateServicesAndCloseCaldecottTunnels(String appName, List<String> services, IProgressMonitor monitor)
      throws CoreException {
    updateServices(appName, services, true, monitor);

  }

  protected void updateServices(final String appName, final List<String> services,
      final boolean closeRelatedCaldecottTunnels, IProgressMonitor monitor) throws CoreException {
    new StagingAwareRequest<Void>("Update services") { //$NON-NLS-1$
      @Override
      protected Void doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
        // Prior to updating the services, obtain the current list of
        // bound services for the app
        // and determine if any services are being unbound. If unbound,
        // check if it is the Caldecott app
        // and accordingly, close related tunnels.
        if (closeRelatedCaldecottTunnels && TunnelBehaviour.isCaldecottApp(appName)) {

          List<String> caldecottServicesToClose = findCaldecottTunnelsToClose(client, appName, services);
          // Close tunnels before the services are removed
          if (caldecottServicesToClose != null) {
            TunnelBehaviour handler = new TunnelBehaviour(getCloudFoundryServer());

            for (String serviceName : caldecottServicesToClose) {
              handler.stopAndDeleteCaldecottTunnel(serviceName, progress);
            }
          }
        }

        client.updateApplicationServices(appName, services);

        return null;
      }
    }.run(monitor);
  }

  public void register(final String email, final String password, IProgressMonitor monitor) throws CoreException {
    new BehaviourRequest<Void>("Registering account") { //$NON-NLS-1$
      @Override
      protected Void doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
        client.register(email, password);
        return null;
      }
    }.run(monitor);
  }

  /**
   * Gets the active client used by the behaviour for server operations.
   * However, clients are created lazily, and invoking it multipe times does
   * not recreate the client, as only one client is created per lifecycle of
   * the server behaviour (but not necessarily per connection session, as the
   * server behaviour may be created and disposed multiple times by the WST
   * framework). To use the server-stored credentials, pass null credentials.
   * <p/>
   * This API is not suitable to changing credentials. User appropriate API
   * for the latter like {@link #updatePassword(String, IProgressMonitor)}
   */
  protected synchronized CloudFoundryOperations getClient(CloudCredentials credentials, IProgressMonitor monitor)
      throws CoreException {
    if (client == null) {
      CloudFoundryServer cloudServer = getCloudFoundryServer();

      String url = cloudServer.getUrl();
      if (!cloudServer.hasCloudSpace()) {
        throw CloudErrorUtil.toCoreException(NLS.bind(Messages.ERROR_FAILED_CLIENT_CREATION_NO_SPACE,
            cloudServer.getServerId()));
      }

      CloudFoundrySpace cloudFoundrySpace = cloudServer.getCloudFoundrySpace();

      if (credentials != null) {
        client = createClient(url, credentials, cloudFoundrySpace, cloudServer.getSelfSignedCertificate());
      }
      else {
        String userName = getCloudFoundryServer().getUsername();
        String password = getCloudFoundryServer().getPassword();
        client = createClient(url, userName, password, cloudFoundrySpace,
            cloudServer.getSelfSignedCertificate());
      }
    }
    return client;
  }

  /**
   * In most cases, the progress monitor can be null, although if available
   *
   * @param monitor
   * @return
   * @throws CoreException
   */
  protected synchronized CloudFoundryOperations getClient(IProgressMonitor monitor) throws CoreException {
    return getClient((CloudCredentials) null, monitor);
  }

  /**
   * Updates the application's environment variables. Note that the
   * application needs to first exist in the server, and be in a state that
   * will accept environment variable changes (either stopped, or running
   * after staging has completed). WARNING: The {@link CloudApplication}
   * mapping in the module WILL be updated if the environment variable update
   * is successful, which will replace any existing deployment info in the app
   * module.
   * @param appModule
   * @param monitor
   * @throws CoreException
   */
  public void updateEnvironmentVariables(final CloudFoundryApplicationModule appModule, IProgressMonitor monitor)
      throws CoreException {
    new BehaviourRequest<Void>("Updating environment variables") { //$NON-NLS-1$

      @Override
      protected Void doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
        // Update environment variables.
        Map<String, String> varsMap = new HashMap<String, String>();

        SubMonitor subProgress = SubMonitor.convert(progress);
        subProgress
            .setTaskName("Updating environment variables for: " + appModule.getDeployedApplicationName()); //$NON-NLS-1$

        try {
          List<EnvironmentVariable> vars = appModule.getDeploymentInfo().getEnvVariables();

          if (vars != null) {
            for (EnvironmentVariable var : vars) {
              varsMap.put(var.getVariable(), var.getValue());
            }
          }

          client.updateApplicationEnv(appModule.getDeployedApplicationName(), varsMap);

          // Update the cloud application which contains the updated
          // environment variables.
          CloudApplication cloudApplication = getApplication(appModule.getDeployedApplicationName(),
              subProgress);
          appModule.setCloudApplication(cloudApplication);

        }
        finally {
          subProgress.done();
        }

        return null;
      }

    }.run(monitor);

  }

  private boolean isApplicationReady(CloudApplication application) {
    /*
     * RestTemplate restTemplate = new RestTemplate(); String response =
     * restTemplate.getForObject(application.getUris().get(0),
     * String.class); if
     * (response.contains("B29 ROUTER: 404 - FILE NOT FOUND")) { return
     * false; }
     */
    return AppState.STARTED.equals(application.getState());
  }

  private boolean waitForStart(CloudFoundryOperations client, String deploymentId, IProgressMonitor monitor)
      throws InterruptedException {
    long initialInterval = CloudOperationsConstants.SHORT_INTERVAL;
    Thread.sleep(initialInterval);
    long timeLeft = CloudOperationsConstants.DEPLOYMENT_TIMEOUT - initialInterval;
    while (timeLeft > 0) {
      CloudApplication deploymentDetails = client.getApplication(deploymentId);
      if (isApplicationReady(deploymentDetails)) {
        return true;
      }
      Thread.sleep(CloudOperationsConstants.ONE_SECOND_INTERVAL);
      timeLeft -= CloudOperationsConstants.ONE_SECOND_INTERVAL;
    }
    return false;
  }

  private CloudApplication getDeployedCloudApplication(CloudFoundryOperations client, String applicationId,
      IProgressMonitor monitor) {
    long timeLeft = CloudOperationsConstants.UPLOAD_TIMEOUT;
    while (timeLeft > 0) {
      CloudApplication application = client.getApplication(applicationId);
      if (applicationId.equals(application.getName())) {
        return application;
      }
      try {
        Thread.sleep(CloudOperationsConstants.SHORT_INTERVAL);
      }
      catch (InterruptedException e) {
        // Ignore. Try again until time runs out
      }
      timeLeft -= CloudOperationsConstants.SHORT_INTERVAL;
    }
    return null;
  }

  /**
   * Will fetch the latest list of cloud applications from the server, and
   * update the local module mappings accordingly.
   * @param cloudServer
   * @param monitor
   * @return true if refresh was performed. False otherwise.
   */

  @Override
  protected void initialize(IProgressMonitor monitor) {
    super.initialize(monitor);
    getServer().addServerListener(serverListener, ServerEvent.SERVER_CHANGE);
    try {
      refreshHandler = new RefreshHandler(getCloudFoundryServer());

      // Be sure to refresh list of all deployed applications when a
      // server instance is initialised. Otherwise, external modules may
      // not appear
      // in the list of Server apps until after some manual refresh
      // occurs, or another operation
      // triggers a refresh.
      // Launch in Job as it could be long-running
      Job job = new Job(NLS.bind(Messages.REFRESHING_MODULES, getServer().getName())) {

        @Override
        protected IStatus run(IProgressMonitor monitor) {
          refreshModules(monitor);
          return Status.OK_STATUS;
        }

      };

      job.setPriority(Job.INTERACTIVE);
      job.schedule();

    }
    catch (CoreException e) {
      CloudFoundryPlugin.logError(Messages.ERROR_INITIALISE_REFRESH_NO_SERVER);
    }
  }

  /**
   * If found, will attempt to publish module with the given name, and it
   * assumes it is being added for the first time. NOTE: This method is only
   * intended to bypass the WST framework in cases not supported by WST (for
   * example, drag/drop an application to a non-WST view or UI control).
   * Otherwise, WST-based deployments of applications (e.g. Run on Server,
   * drag/drop to Servers view) should rely on the framework to invoke the
   * appropriate publish method in the behaviour.
   *
   * @see #publishModule(int, int, IModule[], IProgressMonitor)
   * @param moduleName
   * @param monitor
   * @return status of publish
   */
  public IStatus publishAdd(String moduleName, IProgressMonitor monitor) {
    List<IModule[]> allModules = getAllModules();
    try {
      for (IModule[] module : allModules) {
        if (module[0].getName().equals(moduleName)) {
          new PushApplicationOperation(module).run(monitor);
          return Status.OK_STATUS;
        }
      }
    }
    catch (CoreException ce) {
      handlePublishError(ce);
      return Status.CANCEL_STATUS;
    }
    return Status.OK_STATUS;
  }

  /**
   * Judges whether there is a <code>CloudFoundryApplicationModule</code> with
   * the given name in current server or not.
   *
   * @param moduleName the module name to be checked
   * @return true if there is a <code>CloudFoundryApplicationModule</code>
   * with the given name in current server, false otherwise
   */
  public boolean existCloudApplicationModule(String moduleName) {
    List<IModule[]> allModules = getAllModules();
    for (IModule[] modules : allModules) {
      if (modules[0] instanceof CloudFoundryApplicationModule && modules[0].getName().equals(moduleName)) {
        return true;
      }
    }
    return false;
  }

  protected void handlePublishError(CoreException e) {
    // Do not automatically delete apps on errors, even
    // if critical errors
    // as there may be features that may allow an app to
    // be redeployed without drag/drop (i.e. clicking
    // "Start").
    IStatus errorStatus = CloudFoundryPlugin.getErrorStatus(NLS.bind(Messages.ERROR_FAILED_TO_PUSH_APP,
        e.getMessage()));
    CloudFoundryPlugin.log(errorStatus);
    CloudFoundryPlugin.getCallback().handleError(errorStatus, BehaviourEventType.APP_START);
  }

  @Override
  protected void publishModule(int kind, int deltaKind, IModule[] module, IProgressMonitor monitor)
      throws CoreException {
    super.publishModule(kind, deltaKind, module, monitor);

    try {
      // If the delta indicates that the module has been removed, remove
      // it
      // from the server.
      // Note that although the "module" parameter is of IModule[] type,
      // documentation
      // (and the name of the parameter) indicates that it is always one
      // module
      if (deltaKind == REMOVED) {
        final CloudFoundryServer cloudServer = getCloudFoundryServer();
        final CloudFoundryApplicationModule cloudModule = cloudServer.getCloudModule(module[0]);
        if (cloudModule.getApplication() != null) {
          new BehaviourRequest<Void>(NLS.bind(Messages.DELETING_MODULE,
              cloudModule.getDeployedApplicationName())) {
            @Override
            protected Void doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
              client.deleteApplication(cloudModule.getDeployedApplicationName());
              return null;
            }
          }.run(monitor);
        }

      }
      else if (!module[0].isExternal()) {
        // These operations must ONLY be performed on NON-EXTERNAL
        // applications (apps with associated accessible workspace
        // projects).
        // Do not perform any updates or restarts on non-workspace
        // (external) apps, as some spaces may contain long-running
        // applications that
        // should not be restarted.
        int publishState = getServer().getModulePublishState(module);
        ICloudFoundryOperation op = null;
        if (deltaKind == ServerBehaviourDelegate.ADDED || publishState == IServer.PUBLISH_STATE_UNKNOWN) {
          // Application has not been published, so do a full
          // publish
          op = new PushApplicationOperation(module);
        }
        else if (deltaKind == ServerBehaviourDelegate.CHANGED) {
          op = getApplicationOperation(module, ApplicationAction.UPDATE_RESTART);
        }
        // Republish the root module if any of the child module requires
        // republish
        else if (isChildModuleChanged(module, monitor)) {
          op = getApplicationOperation(module, ApplicationAction.UPDATE_RESTART);
        }

        // NOTE: No need to run this as a separate Job, as publish
        // operations
        // are already run in a PublishJob. To better integrate with
        // WST, ensure publish operation
        // is run to completion in the PublishJob, unless launching
        // asynch events to notify other components while the main
        // publish operation is being run (e.g refresh UI, etc..).
        if (op != null) {
          op.run(monitor);
        }
      }
    }
    catch (CoreException e) {
      handlePublishError(e);
      throw e;
    }
  }

  private boolean isChildModuleChanged(IModule[] module, IProgressMonitor monitor) {
    if (module == null || module.length == 0) {
      return false;
    }

    IServer myserver = this.getServer();
    IModule[] childModules = myserver.getChildModules(module, monitor);

    if (childModules != null && childModules.length > 0) {
      // Compose the full structure of the child module
      IModule[] currentChild = new IModule[module.length + 1];
      for (int i = 0; i < module.length; i++) {
        currentChild[i] = module[i];
      }
      for (IModule child : childModules) {
        currentChild[module.length] = child;

        if (myserver.getModulePublishState(currentChild) != IServer.PUBLISH_STATE_NONE
            || isChildModuleChanged(currentChild, monitor)) {
          return true;
        }
      }
    }
    return false;
  }

  /**
   * Determines if a server supports debug mode. Typically this would be a
   * cached value for performance reasons, and will not reflect changes
   */
  public synchronized boolean isServerDebugModeAllowed() {
    return isDebugModeSupported == DebugSupportCheck.SUPPORTED;
  }

  /**
   * Obtains the debug mode type of the given module. Note that the module
   * need not be started. It could be stopped, and still have a debug mode
   * associated with it.
   * @param module
   * @param monitor
   * @return
   */
  public DebugModeType getDebugModeType(IModule module, IProgressMonitor monitor) {
    try {
      CloudFoundryServer cloudServer = getCloudFoundryServer();
      CloudFoundryApplicationModule cloudModule = cloudServer.getExistingCloudModule(module);

      if (cloudModule == null) {
        CloudFoundryPlugin.logError("No cloud application module found for: " + module.getId() //$NON-NLS-1$
            + ". Unable to determine debug mode."); //$NON-NLS-1$
        return null;
      }

      // Check if a cloud application exists (i.e., it is deployed) before
      // determining if it is deployed in debug mode
      CloudApplication cloudApplication = cloudModule.getApplication();

      if (cloudApplication != null) {
        return DebugModeType.getDebugModeType(cloudApplication.getDebug());
      }

    }
    catch (CoreException e) {
      CloudFoundryPlugin.logError(e);
    }
    return null;
  }

  /**
   * Given a WTP module, the corresponding CF application module will have its
   * app instance stats updated. As the application module also has a
   * reference to the actual cloud application, an updated cloud application
   * will be mapped.
   * @param module whos application instances and stats should be updated
   * @param monitor
   * @throws CoreException
   */
  public void updateApplicationInstanceStats(IModule module, IProgressMonitor monitor) throws CoreException {
    if (module != null) {

      CloudFoundryApplicationModule appModule = getCloudFoundryServer().getExistingCloudModule(module);

      if (appModule != null) {
        internalUpdateAppStats(appModule, monitor);
      }
    }
  }

  /**
   * True if the application is running. False otherwise. Note that an
   * application refresh is performed on the cloud module, therefore the
   * mapping between the cloud application and the module will always be
   * updated with this call.
   * @param appModule
   * @param monitor
   * @return true if application is running. False otherwise.
   */
  public boolean isApplicationRunning(CloudFoundryApplicationModule appModule, IProgressMonitor monitor) {
    try {
      // Refresh the stats FIRST before checking for the app state, as
      // stat refresh will upate the cloud application mapping (and
      // therefore also update the app state)
      return internalUpdateAppStats(appModule, monitor) && appModule.getApplication() != null
          && isApplicationReady(appModule.getApplication());
    }
    catch (CoreException e) {
      CloudFoundryPlugin.logError(e);
    }
    return false;
  }

  /**
   * Retrieves the orgs and spaces for the current server instance.
   * @param monitor
   * @return
   * @throws CoreException if it failed to retrieve the orgs and spaces.
   */
  public CloudOrgsAndSpaces getCloudSpaces(IProgressMonitor monitor) throws CoreException {
    return new BehaviourRequest<CloudOrgsAndSpaces>("Getting orgs and spaces") { //$NON-NLS-1$

      @Override
      protected CloudOrgsAndSpaces doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
        return internalGetCloudSpaces(client);
      }

    }.run(monitor);
  }

  public List<CloudRoute> getRoutes(final String domainName, IProgressMonitor monitor) throws CoreException {

    List<CloudRoute> routes = new BehaviourRequest<List<CloudRoute>>(NLS.bind(Messages.ROUTES, domainName)) {
      @Override
      protected List<CloudRoute> doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
        return client.getRoutes(domainName);
      }
    }.run(monitor);

    return routes;
  }

  public void deleteRoute(final List<CloudRoute> routes, IProgressMonitor monitor) throws CoreException {

    if (routes == null || routes.isEmpty()) {
      return;
    }
    new BehaviourRequest<Void>("Deleting routes") { //$NON-NLS-1$
      @Override
      protected Void doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
        for (CloudRoute route : routes) {
          client.deleteRoute(route.getHost(), route.getDomain().getName());
        }
        return null;

      }
    }.run(monitor);
  }

  /**
   * Attempts to retrieve cloud spaces using the given set of credentials and
   * server URL. This bypasses the session client in a Cloud Foundry server
   * instance, if one exists for the given server URL, and therefore attempts
   * to retrieve the cloud spaces with a disposable, temporary client that
   * logs in with the given credentials.Therefore, if fetching orgs and spaces
   * from an existing server instance, please use
   * {@link CloudFoundryServerBehaviour#getCloudSpaces(IProgressMonitor)}.
   * @param client
   * @param selfSigned true if connecting to a self-signing server. False
   * otherwise
   * @param monitor which performs client login checks, and basic error
   * handling. False if spaces should be obtained directly from the client
   * API.
   *
   * @return resolved orgs and spaces for the given credential and server URL.
   */
  public static CloudOrgsAndSpaces getCloudSpacesExternalClient(CloudCredentials credentials, final String url,
      boolean selfSigned, IProgressMonitor monitor) throws CoreException {

    final CloudFoundryOperations operations = CloudFoundryServerBehaviour.createExternalClientLogin(url,
        credentials.getEmail(), credentials.getPassword(), selfSigned, monitor);

    return new ClientRequest<CloudOrgsAndSpaces>("Getting orgs and spaces") { //$NON-NLS-1$
      @Override
      protected CloudOrgsAndSpaces doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
        return internalGetCloudSpaces(client);
      }

      @Override
      protected CloudFoundryOperations getClient(IProgressMonitor monitor) throws CoreException {
        return operations;
      }

    }.run(monitor);

  }

  /**
   * This should be called within a {@link ClientRequest}, as it makes a
   * direct client call.
   * @param client
   * @return
   */
  private static CloudOrgsAndSpaces internalGetCloudSpaces(CloudFoundryOperations client) {
    List<CloudSpace> foundSpaces = client.getSpaces();
    if (foundSpaces != null && !foundSpaces.isEmpty()) {
      List<CloudSpace> actualSpaces = new ArrayList<CloudSpace>(foundSpaces);
      CloudOrgsAndSpaces orgsAndSpaces = new CloudOrgsAndSpaces(actualSpaces);
      return orgsAndSpaces;
    }

    return null;
  }

  /**
   * Updates the application instances stats for the given cloud application
   * module. It does not update the Cloud app module -> cloud application
   * mapping.
   * @param appModule cannot be null.
   * @param monitor
   * @throws CoreException error in retrieving application instances stats
   * from the server.
   * @return true if application stats are refreshed (application is running).
   * False is application is not running and stats could not be fetched.
   */
  protected boolean internalUpdateAppStats(CloudFoundryApplicationModule appModule, IProgressMonitor monitor)
      throws CoreException {

    // Update the CloudApplication in the cloud module.
    CloudApplication application = getApplication(appModule.getDeployedApplicationName(), monitor);
    appModule.setCloudApplication(application);

    if (application == null) {
      throw CloudErrorUtil.toCoreException(Messages.ERROR_NO_CLOUD_APPLICATION_FOUND);
    }

    InstancesInfo info = internalUpdateInstancesInfo(appModule, monitor);
    ApplicationStats stats = internalUpdateStats(appModule, monitor);
    return info != null && stats != null;
  }

  protected ApplicationStats internalUpdateStats(CloudFoundryApplicationModule appModule, IProgressMonitor monitor)
      throws CoreException {
    ApplicationStats stats = getApplicationStats(appModule.getDeployedApplicationName(), monitor);
    appModule.setApplicationStats(stats);

    return stats;
  }

  protected InstancesInfo internalUpdateInstancesInfo(CloudFoundryApplicationModule appModule,
      IProgressMonitor monitor) throws CoreException {
    InstancesInfo info = getInstancesInfo(appModule.getDeployedApplicationName(), monitor);
    appModule.setInstancesInfo(info);

    return info;
  }

  public static void validate(final String location, String userName, String password, boolean selfSigned,
      IProgressMonitor monitor) throws CoreException {
    createExternalClientLogin(location, userName, password, selfSigned, monitor);
  }

  public static CloudFoundryOperations createExternalClientLogin(final String location, String userName,
      String password, boolean selfSigned, IProgressMonitor monitor) throws CoreException {
    SubMonitor progress = SubMonitor.convert(monitor);
    progress.beginTask("Connecting", IProgressMonitor.UNKNOWN); //$NON-NLS-1$
    try {
      final CloudFoundryOperations client = createClient(location, userName, password, selfSigned);

      new ClientRequest<Void>(Messages.VALIDATING_CREDENTIALS) {

        @Override
        protected Void doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
          CloudFoundryLoginHandler operationsHandler = new CloudFoundryLoginHandler(client);
          int attempts = 5;
          operationsHandler.login(progress, attempts, CloudOperationsConstants.LOGIN_INTERVAL);
          return null;
        }

        @Override
        protected CloudFoundryOperations getClient(IProgressMonitor monitor) throws CoreException {
          return client;
        }

      }.run(monitor);
      return client;
    }
    catch (RuntimeException t) {
      throw CloudErrorUtil.checkServerCommunicationError(t);
    }
    finally {
      progress.done();
    }
  }

  public static void register(String location, String userName, String password, boolean selfSigned,
      IProgressMonitor monitor) throws CoreException {
    SubMonitor progress = SubMonitor.convert(monitor);
    progress.beginTask("Connecting", IProgressMonitor.UNKNOWN); //$NON-NLS-1$
    try {
      CloudFoundryOperations client = createClient(location, userName, password, selfSigned);
      client.register(userName, password);
    }
    catch (RestClientException e) {
      throw CloudErrorUtil.toCoreException(e);
    }
    catch (RuntimeException e) {
      // try to guard against IOException in parsing response
      throw CloudErrorUtil.checkServerCommunicationError(e);

    }
    finally {
      progress.done();
    }
  }

  /**
   * Creates a standalone client with no association with a server behaviour.
   * This is used only for connecting to a Cloud Foundry server for credential
   * verification. The session client for the server behaviour is created when
   * the latter is created
   * @param location
   * @param userName
   * @param password
   * @param selfSigned true if connecting to self-signing server. False
   * otherwise
   * @return
   * @throws CoreException
   */
  static CloudFoundryOperations createClient(String location, String userName, String password, boolean selfSigned)
      throws CoreException {
    return createClient(location, userName, password, null, selfSigned);
  }

  /**
   * Creates a new client to the given server URL using the specified
   * credentials. This does NOT connect the client to the server, nor does it
   * set the client as a session client for the server delegate.
   *
   * @param serverURL must not be null
   * @param userName must not be null
   * @param password must not be null
   * @param cloudSpace optional, as a valid client can still be created
   * without org/space (for example, a client can be used to do an org/space
   * lookup.
   * @param selfSigned true if connecting to self-signed server. False
   * otherwise
   * @return Non-null client.
   * @throws CoreException if failed to create the client.
   */
  private static CloudFoundryOperations createClient(String serverURL, String userName, String password,
      CloudFoundrySpace cloudSpace, boolean selfSigned) throws CoreException {
    if (password == null) {
      // lost the password, start with an empty one to avoid assertion
      // error
      password = ""; //$NON-NLS-1$
    }
    return createClient(serverURL, new CloudCredentials(userName, password), cloudSpace, selfSigned);
  }

  /**
   * Creates a new client to the specified server URL using the given
   * credentials. This does NOT connect the client to the server, nor does it
   * set the client as the session client for the server behaviour. The
   * session client is set indirectly via {@link #connect(IProgressMonitor)}
   * @param serverURL server to connect to. Must NOT be null.
   * @param credentials must not be null.
   * @param cloudSpace optional. Can be null, as a client can be created
   * without specifying an org/space (e.g. a client can be created for the
   * purpose of looking up all the orgs/spaces in a server)
   * @param selfSigned true if connecting to a server with self signed
   * certificate. False otherwise
   * @return non-null client.
   * @throws CoreException if failed to create client.
   */
  private static CloudFoundryOperations createClient(String serverURL, CloudCredentials credentials,
      CloudFoundrySpace cloudSpace, boolean selfSigned) throws CoreException {

    URL url;
    try {
      url = new URL(serverURL);
      int port = url.getPort();
      if (port == -1) {
        port = url.getDefaultPort();
      }

      // If no cloud space is specified, use appropriate client factory
      // API to create a non-space client
      // NOTE that using a space API with null org and space will result
      // in errors as that API will
      // expect valid org and space values.
      return cloudSpace != null ? CloudFoundryPlugin.getCloudFoundryClientFactory().getCloudFoundryOperations(
          credentials, url, cloudSpace.getOrgName(), cloudSpace.getSpaceName(), selfSigned)
          : CloudFoundryPlugin.getCloudFoundryClientFactory().getCloudFoundryOperations(credentials, url,
              selfSigned);
    }
    catch (MalformedURLException e) {
      throw new CoreException(new Status(IStatus.ERROR, CloudFoundryPlugin.PLUGIN_ID,
          "The server url " + serverURL + " is invalid: " + e.getMessage(), e)); //$NON-NLS-1$ //$NON-NLS-2$
    }
  }

  // public static class RequestFactory extends
  // CommonsClientHttpRequestFactory {
  //
  // private HttpClient client;
  //
  // /**
  // * For testing.
  // */
  // public static boolean proxyEnabled = true;
  //
  // public RequestFactory(HttpClient client) {
  // super(client);
  // this.client = client;
  // }
  //
  // public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod)
  // throws IOException {
  // IProxyData[] proxy =
  // CloudFoundryPlugin.getDefault().getProxyService().select(uri);
  // if (proxyEnabled && proxy != null && proxy.length > 0) {
  // client.getHostConfiguration().setProxy(proxy[0].getHost(),
  // proxy[0].getPort());
  // }else {
  // client.getHostConfiguration().setProxyHost(null);
  // }
  // return super.createRequest(uri, httpMethod);
  // }
  //
  // }

  /**
   *
   * Request that is aware of potential staging related errors and may attempt
   * the request again on certain types of staging errors like Staging Not
   * Finished errors.
   * <p/>
   * Because the set of client operations wrapped around this Request may be
   * attempted again on certain types of errors, it's best to keep the set of
   * client operations as minimal as possible, to avoid performing client
   * operations again that had no errors.
   *
   * <p/>
   * Note that this should only be used around certain types of operations
   * performed on a app that is already started, like fetching the staging
   * logs, or app instances stats, as re-attempts on these operations due to
   * staging related errors (e.g. staging not finished yet) is permissable.
   *
   * <p/>
   * However, operations not related an application being in a running state
   * (e.g. creating a service, getting list of all apps), should not use this
   * request.
   */
  abstract class StagingAwareRequest<T> extends BehaviourRequest<T> {

    public StagingAwareRequest(String label) {
      super(label);
    }

    protected long getWaitInterval(Throwable exception, SubMonitor monitor) throws CoreException {

      if (exception instanceof CoreException) {
        exception = ((CoreException) exception).getCause();
      }

      if (exception instanceof NotFinishedStagingException) {
        return CloudOperationsConstants.ONE_SECOND_INTERVAL * 2;
      }
      else if (exception instanceof CloudFoundryException
          && CloudErrorUtil.isAppStoppedStateError((CloudFoundryException) exception)) {
        return CloudOperationsConstants.ONE_SECOND_INTERVAL;
      }
      return -1;
    }

    protected abstract T doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException;

  }

  /**
   *
   * Reattempts the operation if a app in stopped state error is encountered.
   *
   */
  abstract class AppInStoppedStateAwareRequest<T> extends BehaviourRequest<T> {

    public AppInStoppedStateAwareRequest(String label) {
      super(label);
    }

    protected long getWaitInterval(Throwable exception, SubMonitor monitor) throws CoreException {

      if (exception instanceof CoreException) {
        exception = ((CoreException) exception).getCause();
      }

      if (exception instanceof CloudFoundryException
          && CloudErrorUtil.isAppStoppedStateError((CloudFoundryException) exception)) {
        return CloudOperationsConstants.ONE_SECOND_INTERVAL;
      }
      return -1;
    }

    protected abstract T doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException;

  }

  /**
   * Returns an executable application operation based on the given Cloud
   * Foundry application module and an application start mode (
   * {@link ApplicationAction} ).
   * <p/>
   * Throws error if failure occurred while attempting to resolve an
   * operation. If no operation is resolved and no errors occurred while
   * attempting to resolve an operation, null is returned, meaning that no
   * operation is currently defined for the given deployment mode.
   * <p/>
   * It does NOT execute the operation.
   * @param application
   * @param action
   * @return resolved executable operation associated with the given
   * deployment mode, or null if an operation could not be resolved.
   * @throws CoreException
   */
  public ICloudFoundryOperation getApplicationOperation(CloudFoundryApplicationModule application,
      ApplicationAction action) throws CoreException {
    IModule[] modules = new IModule[] { application.getLocalModule() };

    return getApplicationOperation(modules, action);
  }

  public ICloudFoundryOperation getApplicationOperation(IModule[] modules, ApplicationAction action)
      throws CoreException {
    ICloudFoundryOperation operation = null;
    // Set the deployment mode
    switch (action) {
    case START:
      operation = internalGetDeployStartApplicationOperation(modules);
      break;
    case STOP:
      operation = getStopAppOperation(modules);
      break;
    case RESTART:
      operation = getRestartOperation(modules);
      break;
    case UPDATE_RESTART:
      operation = getUpdateRestartOperation(modules);
      break;
    }
    return operation;
  }

  /**
   * Deploys an application and or starts it in regular or debug mode. If
   * deployed in debug mode, an attempt will be made to connect the deployed
   * application to a debugger. An operation should performed atomically PER
   * APPLICATION.
   * <p/>
   * The operation performs some common tasks like checking that the
   * application's deployment info is complete and valid, and that any refresh
   * jobs running in the background are stopped prior to starting the
   * operation, and restarted afterward.
   *
   */
  protected abstract class ApplicationOperation extends AbstractDeploymentOperation {

    final protected IModule[] modules;

    private ICloudFoundryApplicationModule appModule;

    private DeploymentConfiguration configuration;

    protected ApplicationOperation(IModule[] modules) {
      super(CloudFoundryServerBehaviour.this);
      this.modules = modules;
    }

    public ICloudFoundryApplicationModule getApplicationModule() {
      return appModule;
    }

    abstract protected String getOperationName();

    /**
     * The local configuration for the app. It indicates what deployment
     * mode the app should be launched in (e.g. START, STOP..). If a
     * configuration cannot be resolved, a default one will be returned
     * instead.
     * @return deployment configuration. Never null.
     */
    protected DeploymentConfiguration getDeploymentConfiguration() {
      if (configuration == null) {
        configuration = getDefaultDeploymentConfiguration();
      }
      return configuration;
    }

    protected void performOperation(IProgressMonitor monitor) throws CoreException {

      // Given that we only look at the root module for generating the
      // appModule
      // ie: indicated by the following
      // getOrCreateCloudApplicationModule() call
      // we should ignore child modules of this root module so that
      // we don't prompt multiple wizards for the same root module during
      // deployment

      if (modules.length != 1) {
        return;
      }

      CloudFoundryApplicationModule appModule = getOrCreateCloudApplicationModule(modules);

      try {
        CloudFoundryServer cloudServer = getCloudFoundryServer();

        // Stop any consoles
        CloudFoundryPlugin.getCallback().stopApplicationConsole(appModule, cloudServer);

        configuration = prepareForDeployment(appModule, monitor);

        IStatus validationStatus = appModule.validateDeploymentInfo();
        if (!validationStatus.isOK()) {
          throw CloudErrorUtil.toCoreException(NLS.bind(Messages.ERROR_APP_DEPLOYMENT_VALIDATION_ERROR,
              appModule.getDeployedApplicationName(), validationStatus.getMessage()));

        }
        // NOTE: Only print to a console AFTER an application has been
        // prepared for deployment, as the application
        // name may have changed during the deployment preparation
        // stage, and consoles are mapped by application name.
        // This prevents two different consoles with different names
        // from appearing for the same application
        clearAndPrintlnConsole(appModule,
            NLS.bind(Messages.CONSOLE_PREPARING_APP, appModule.getDeployedApplicationName()));

        performDeployment(appModule, monitor);

        // Refresh the application instance stats as well
        try {
          internalUpdateAppStats(appModule, monitor);
        }
        catch (CoreException e) {
          // Don't let errors in app instance stats stop the
          // completion of the ApplicationOperation
          CloudFoundryPlugin.logError(e);
        }
      }
      catch (OperationCanceledException e) {
        // ignore so webtools does not show an exception
        ((Server) getServer()).setModuleState(modules, IServer.STATE_UNKNOWN);
      }
      catch (CoreException ce) {
        // Log the error in console
        printErrorlnToConsole(appModule, ce.getMessage());
        throw ce;
      }

    }

    /**
     * Prepares an application to either be deployed, started or restarted.
     * The main purpose to ensure that the application's deployment
     * information is complete. If incomplete, it will prompt the user for
     * missing information.
     * @param monitor
     * @return Deployment configuration, or null if default configuration
     * should be used.
     * @throws CoreException if any failure during or after the operation.
     * @throws OperationCanceledException if the user cancelled deploying or
     * starting the application. The application's deployment information
     * should not be modified in this case.
     */
    protected DeploymentConfiguration prepareForDeployment(CloudFoundryApplicationModule appModule,
        IProgressMonitor monitor) throws CoreException, OperationCanceledException {
      return null;
    }

    /**
     *
     * @return default deployment configuration, that among other things
     * determines the deployment mode of an application (for example, START,
     * STOP, RESTART). Must not be null.
     */
    protected abstract DeploymentConfiguration getDefaultDeploymentConfiguration();

    /**
     *
     * @param appModule to be deployed or started
     * @param monitor
     * @throws CoreException if error occurred during deployment or starting
     * the app, or resolving the updated cloud application from the client.
     */
    protected abstract void performDeployment(CloudFoundryApplicationModule appModule, IProgressMonitor monitor)
        throws CoreException;
  }

  protected boolean hasChildModules(IModule[] modules) {
    IWebModule webModule = CloudUtil.getWebModule(modules);
    return webModule != null && webModule.getModules() != null && webModule.getModules().length > 0;
  }

  /**
   * This action is the primary operation for uploading and starting an
   * application to a CF server.
   * <p/>
   * It does NOT publish the application if it doesn't exist in the server. It
   * is meant to start and update an application that already exists.
   * <p/>
   *
   * Several primary steps are performed when deploying an application:
   * <p/>
   * 1. Create an archive file containing the application's resources.
   * Incremental publishing is may be used here to create an archive
   * containing only those files that have been changed.
   * <p/>
   * 2. Set local WTP module states to indicate the an application's contents
   * have been pushed (i.e. "published")
   * <p/>
   * 3. Start the application in the server, if specified by the deployment
   * configuration.
   * <p/>
   * 4. Set local WTP module states to indicate whether an application has
   * started, or is stopped if an error occurred while starting it.
   * <p/>
   * 5. Invoke callbacks to notify listeners that an application has been
   * started. One of the notification is to the CF console to display the app
   * logs in the CF console.
   * <p/>
   */
  protected class StartOperation extends RestartOperation {

    final protected boolean incrementalPublish;

    /**
     *
     * @param waitForDeployment
     * @param incrementalPublish
     * @param modules
     * @param alwaysStart if true, application will always start. if false,
     */
    public StartOperation(boolean incrementalPublish, IModule[] modules) {
      super(modules);
      this.incrementalPublish = incrementalPublish;
    }

    @Override
    protected String getOperationName() {
      return Messages.CONSOLE_DEPLOYING_APP;
    }

    @Override
    protected DeploymentConfiguration getDefaultDeploymentConfiguration() {
      return new DeploymentConfiguration(ApplicationAction.START);
    }

    @Override
    protected void performDeployment(CloudFoundryApplicationModule appModule, IProgressMonitor monitor)
        throws CoreException {
      final Server server = (Server) getServer();
      final CloudFoundryServer cloudServer = getCloudFoundryServer();
      final IModule module = modules[0];

      try {

        // Update the local cloud module representing the application
        // first.
        appModule.setErrorStatus(null);

        server.setModuleState(modules, IServer.STATE_STARTING);

        final String deploymentName = appModule.getDeploymentInfo().getDeploymentName();

        // This request does three things:
        // 1. Checks if the application external or mapped to a local
        // project. If mapped to a local project
        // it creates an archive of the application's content
        // 2. If an archive file was created, it pushes the archive
        // file.
        // 3. While pushing the archive file, a check is made to see if
        // the application exists remotely. If not, the application is
        // created in the
        // CF server.

        if (!modules[0].isExternal()) {

          printlnToConsole(appModule, Messages.CONSOLE_GENERATING_ARCHIVE);

          final ApplicationArchive applicationArchive = generateApplicationArchiveFile(
              appModule.getDeploymentInfo(), appModule, modules, server, incrementalPublish, monitor);
          File warFile = null;
          if (applicationArchive == null) {
            // Create a full war archive
            warFile = CloudUtil.createWarFile(modules, server, monitor);
            if (warFile == null || !warFile.exists()) {
              throw new CoreException(new Status(IStatus.ERROR, CloudFoundryPlugin.PLUGIN_ID,
                  "Unable to create war file for application: " + deploymentName)); //$NON-NLS-1$
            }

            CloudFoundryPlugin.trace("War file " + warFile.getName() + " created"); //$NON-NLS-1$ //$NON-NLS-2$
          }
          // Tell webtools the module has been published
          setModulePublishState(modules, IServer.PUBLISH_STATE_NONE);

          // update server publish status
          IModule[] serverModules = server.getModules();
          boolean allSynched = true;
          for (IModule serverModule : serverModules) {
            int modulePublishState = server.getModulePublishState(new IModule[] { serverModule });
            if (modulePublishState == IServer.PUBLISH_STATE_INCREMENTAL
                || modulePublishState == IServer.PUBLISH_STATE_FULL) {
              allSynched = false;
            }
          }

          if (allSynched) {
            server.setServerPublishState(IServer.PUBLISH_STATE_NONE);
          }

          final File warFileFin = warFile;
          final CloudFoundryApplicationModule appModuleFin = appModule;
          // Now push the application resources to the server

          new BehaviourRequest<Void>("Pushing the application " + deploymentName) { //$NON-NLS-1$
            @Override
            protected Void doRun(final CloudFoundryOperations client, SubMonitor progress)
                throws CoreException {

              pushApplication(client, appModuleFin, warFileFin, applicationArchive, progress);

              CloudFoundryPlugin.trace("Application " + deploymentName //$NON-NLS-1$
                  + " pushed to Cloud Foundry server."); //$NON-NLS-1$

              cloudServer.tagAsDeployed(module);

              return null;
            }

          }.run(monitor);

          printlnToConsole(appModule, Messages.CONSOLE_APP_PUSHED_MESSAGE);

        }

        // Verify the application exists in the server
        CloudApplication application = getDeployedCloudApplication(client,
            appModule.getDeployedApplicationName(), monitor);

        if (application == null) {
          throw CloudErrorUtil
              .toCoreException("No cloud application obtained from the Cloud Foundry server for :  " //$NON-NLS-1$
                  + appModule.getDeployedApplicationName()
                  + ". Application may not have deployed correctly to the Cloud Foundry server, or there are connection problems to the server."); //$NON-NLS-1$
        }

        // At this stage, the app is either created or it already
        // exists.
        // Set the environment variables BEFORE starting the app, and
        // BEFORE updating
        // the cloud application mapping in the module, which will
        // replace the deployment info
        updateEnvironmentVariables(appModule, monitor);

        // Update instances if it is more than 1. By default, app starts
        // with 1 instance.
        int instances = appModule.getDeploymentInfo().getInstances();
        if (instances > 1) {
          updateApplicationInstances(appModule, instances, monitor);
        }

        // If reached here it means the application creation and content
        // pushing probably succeeded without errors, therefore attempt
        // to
        // start the application
        super.performDeployment(appModule, monitor);

      }
      catch (CoreException e) {
        appModule.setErrorStatus(e);
        server.setModulePublishState(modules, IServer.PUBLISH_STATE_UNKNOWN);
        throw e;
      }
    }

    /**
     * This performs the primary operation of creating an application and
     * then pushing the application contents to the server. These are
     * performed in separate requests via the CF client. If the application
     * does not exist, it is first created through an initial request. Once
     * the application is created, or if it already exists, the next step is
     * to upload (push) the application archive containing the application's
     * resources. This is performed in a second separate request.
     * <p/>
     * To avoid replacing the deployment info in the app module, the mapping
     * to the most recent {@link CloudApplication} in the app module is NOT
     * updated with newly created application. It is up to the caller to set
     * the mapping in {@link CloudFoundryApplicationModule}
     * @param client
     * @param appModule valid Cloud module with valid deployment info.
     * @param monitor
     * @throws CoreException if error creating the application
     */
    protected void pushApplication(CloudFoundryOperations client, final CloudFoundryApplicationModule appModule,
        File warFile, ApplicationArchive applicationArchive, final IProgressMonitor monitor)
        throws CoreException {

      String appName = appModule.getDeploymentInfo().getDeploymentName();

      try {
        printlnToConsole(appModule, Messages.CONSOLE_APP_PUSH_MESSAGE);
        // Now push the application content.
        if (warFile != null) {
          client.uploadApplication(appName, warFile);
        }
        else if (applicationArchive != null) {
          // Handle the incremental publish case separately as it
          // requires
          // a partial war file generation of only the changed
          // resources
          // AFTER
          // the server determines the list of missing file names.
          if (applicationArchive instanceof CachingApplicationArchive) {
            final CachingApplicationArchive cachingArchive = (CachingApplicationArchive) applicationArchive;
            client.uploadApplication(appName, cachingArchive, new UploadStatusCallback() {

              public void onProcessMatchedResources(int length) {

              }

              public void onMatchedFileNames(Set<String> matchedFileNames) {
                cachingArchive.generatePartialWarFile(matchedFileNames);
              }

              public void onCheckResources() {

              }

              public boolean onProgress(String status) {
                return false;
              }
            });

            // Once the application has run, do a clean up of the
            // sha1
            // cache for deleted resources

          }
          else {
            client.uploadApplication(appName, applicationArchive, new UploadStatusCallback() {

              public void onProcessMatchedResources(int length) {

              }

              public void onMatchedFileNames(Set<String> matchedFileNames) {
                // try {
                // printlnToConsole(appModule, ".", false,
                // false, monitor);
                // }
                // catch (CoreException e) {
                // CloudFoundryPlugin.logError(e);
                // }
              }

              public void onCheckResources() {

              }

              public boolean onProgress(String status) {
                return false;
              }
            });

          }
        }
        else {
          throw CloudErrorUtil
              .toCoreException("Failed to deploy application " + appModule.getDeploymentInfo().getDeploymentName() + //$NON-NLS-1$
                  " since no deployable war or application archive file was generated."); //$NON-NLS-1$
        }
      }
      catch (IOException e) {
        throw new CoreException(CloudFoundryPlugin.getErrorStatus("Failed to deploy application " + //$NON-NLS-1$
            appModule.getDeploymentInfo().getDeploymentName() + " due to " + e.getMessage(), e)); //$NON-NLS-1$
      }

    }
  }

  /**
   * Operation publish an application. If the application is already deployed
   * and synchronised, it will only update the mapping between the module and
   * the {@link CloudApplication}.
   *
   * <p/>
   * 1. Prompts for deployment information.
   * <p/>
   * 2. Creates the application if the application does not currently exist in
   * the server
   * <p/>
   * 3. Starts the application if specified in the deployment configuration
   * for the application.
   * <p/>
   * If the application is already published (it exists in the server), it
   * will ONLY update the published cloud application mapping in the
   * {@link CloudFoundryApplicationModule}. It will NOT re-create, re-publish,
   * or restart the application.
   * <p/>
   *
   */
  protected class PushApplicationOperation extends StartOperation {

    public PushApplicationOperation(IModule[] modules) {
      super(false, modules);
    }

    @Override
    protected DeploymentConfiguration prepareForDeployment(CloudFoundryApplicationModule appModule,
        IProgressMonitor monitor) throws CoreException {
      // If the app is already published, just refresh the application
      // mapping.
      int moduleState = getServer().getModulePublishState(new IModule[] { appModule.getLocalModule() });
      if (appModule.isDeployed() && moduleState == IServer.PUBLISH_STATE_NONE) {

        printlnToConsole(appModule, Messages.CONSOLE_APP_FOUND);

        CloudApplication cloudApp = null;

        printlnToConsole(appModule,
            NLS.bind(Messages.CONSOLE_APP_MAPPING_STARTED, appModule.getDeployedApplicationName()));
        try {
          cloudApp = getApplication(appModule.getDeployedApplicationName(), monitor);
          appModule.setCloudApplication(cloudApp);
          printlnToConsole(appModule,
              NLS.bind(Messages.CONSOLE_APP_MAPPING_COMPLETED, appModule.getDeployedApplicationName()));

        }
        catch (CoreException e) {
          // Do not log the error. The application may not exist
          // anymore. If it is a network error, it will become evident
          // in further steps
        }
      }
      else {
        try {
          CloudFoundryServer cloudServer = getCloudFoundryServer();

          // prompt user for missing details
          return CloudFoundryPlugin.getCallback().prepareForDeployment(cloudServer, appModule, monitor);
        }
        catch (OperationCanceledException oce) {
          // Prepare for deployment prompts the user for missing
          // information for a non-published app. If a user
          // cancels
          // delete the application module
          getCloudFoundryServer().doDeleteModules(Arrays.asList(modules));
          throw oce;
        }
      }
      return null;
    }

    @Override
    protected void pushApplication(CloudFoundryOperations client, final CloudFoundryApplicationModule appModule,
        File warFile, ApplicationArchive applicationArchive, final IProgressMonitor monitor)
        throws CoreException {
      String appName = appModule.getDeploymentInfo().getDeploymentName();

      List<CloudApplication> existingApps = client.getApplications();
      boolean found = false;
      for (CloudApplication existingApp : existingApps) {
        if (existingApp.getName().equals(appName)) {
          found = true;
          break;
        }
      }

      // Create the application if it doesn't already exist
      if (!found) {

        printlnToConsole(appModule, Messages.CONSOLE_APP_CREATION);

        Staging staging = appModule.getDeploymentInfo().getStaging();
        List<String> uris = appModule.getDeploymentInfo().getUris() != null ? appModule.getDeploymentInfo()
            .getUris() : new ArrayList<String>(0);
        List<String> services = appModule.getDeploymentInfo().asServiceBindingList();

        if (staging == null) {
          // For v2, a non-null staging is required.
          staging = new Staging();
        }
        client.createApplication(appName, staging, appModule.getDeploymentInfo().getMemory(), uris, services);
      }
      super.pushApplication(client, appModule, warFile, applicationArchive, monitor);
    }

    @Override
    protected void performDeployment(CloudFoundryApplicationModule appModule, IProgressMonitor monitor)
        throws CoreException {
      if (!appModule.isDeployed()) {
        super.performDeployment(appModule, monitor);
      }
    }

  }

  /**
   *
   * @param descriptor that contains the application information, and that
   * also will be updated with an archive containing the application resources
   * to be deployed to the Cloud Foundry Server
   * @param cloudModule the Cloud Foundry wrapper around the application
   * module to be pushed to the server
   * @param modules list of WTP modules.
   * @param server where app should be pushed to
   * @param
   * @param monitor
   * @throws CoreException if failure occurred while generated an archive file
   * containing the application's payload
   */
  protected ApplicationArchive generateApplicationArchiveFile(ApplicationDeploymentInfo deploymentInfo,
      CloudFoundryApplicationModule cloudModule, IModule[] modules, Server server, boolean incrementalPublish,
      IProgressMonitor monitor) throws CoreException {

    // Perform local operations like building an archive file
    // and payload for the application
    // resources prior to pushing it to the server.

    // If the module is not external (meaning that it is
    // mapped to a local, accessible workspace project),
    // create an
    // archive file containing changes to the
    // application's
    // resources. Use incremental publishing if
    // possible.

    AbstractApplicationDelegate delegate = ApplicationRegistry.getApplicationDelegate(cloudModule.getLocalModule());

    ApplicationArchive archive = null;
    if (delegate != null && delegate.providesApplicationArchive(cloudModule.getLocalModule())) {
      IModuleResource[] resources = getResources(modules);

      archive = getApplicationArchive(cloudModule, monitor, delegate, resources);
    }

    // If no application archive was provided,then attempt an incremental
    // publish. Incremental publish is only supported for apps without child
    // modules.
    if (archive == null && incrementalPublish && !hasChildModules(modules)) {
      // Determine if an incremental publish
      // should
      // occur
      // For the time being support incremental
      // publish
      // only if the app does not have child
      // modules
      // To compute incremental deltas locally,
      // modules must be provided
      // Computes deltas locally before publishing
      // to
      // the server.
      // Potentially more efficient. Should be
      // used
      // only on incremental
      // builds

      archive = getIncrementalPublishArchive(deploymentInfo, modules);
    }
    return archive;

  }

  private ApplicationArchive getApplicationArchive(CloudFoundryApplicationModule cloudModule,
      IProgressMonitor monitor, AbstractApplicationDelegate delegate, IModuleResource[] resources)
      throws CoreException {
    SubMonitor subProgress = SubMonitor.convert(monitor);
    subProgress.setTaskName("Creating application archive for: " + cloudModule.getDeployedApplicationName()); //$NON-NLS-1$

    ApplicationArchive archive = null;
    try {
      archive = delegate.getApplicationArchive(cloudModule, getCloudFoundryServer(), resources, monitor);
    }
    finally {
      subProgress.done();
    }
    return archive;
  }

  /**
   * Note that consoles may be mapped to an application's deployment name. If
   * during deployment, the application name has changed, then this may result
   * in two separate consoles.
   *
   *
   * @param appModule consoles are associated with a particular deployed
   * application. This must not be null.
   * @param message
   * @param clearConsole true if console should be cleared. False, if message
   * should be tailed to existing content in the console.
   * @param runningOperation if it is a message related to an ongoing
   * operation, which will append "..." to the message
   * @throws CoreException
   */
  protected void clearAndPrintlnConsole(CloudFoundryApplicationModule appModule, String message) throws CoreException {
    message += '\n';
    printToConsole(appModule, message, true, false);
  }

  protected void printlnToConsole(CloudFoundryApplicationModule appModule, String message) throws CoreException {
    message += '\n';
    printToConsole(appModule, message, false, false);
  }

  protected void printErrorlnToConsole(CloudFoundryApplicationModule appModule, String message) throws CoreException {
    message = NLS.bind(Messages.CONSOLE_ERROR_MESSAGE + '\n', message);
    printToConsole(appModule, message, false, true);
  }

  /**
   * Note that consoles may be mapped to an application's deployment name. If
   * during deployment, the application name has changed, then this may result
   * in two separate consoles.
   *
   */
  protected void printToConsole(CloudFoundryApplicationModule appModule, String message, boolean clearConsole,
      boolean isError) throws CoreException {
    CloudFoundryPlugin.getCallback().printToConsole(getCloudFoundryServer(), appModule, message, clearConsole,
        isError);
  }

  protected ApplicationArchive getIncrementalPublishArchive(final ApplicationDeploymentInfo deploymentInfo,
      IModule[] modules) {
    IModuleResource[] allResources = getResources(modules);
    IModuleResourceDelta[] deltas = getPublishedResourceDelta(modules);
    List<IModuleResource> changedResources = getChangedResources(deltas);
    ApplicationArchive moduleArchive = new CachingApplicationArchive(Arrays.asList(allResources), changedResources,
        modules[0], deploymentInfo.getDeploymentName());

    return moduleArchive;
  }

  /**
   *
   * Attempts to start an application. It does not create an application, or
   * incrementally or fully push the application's resources. It simply starts
   * the application in the server with the application's currently published
   * resources, regardless of local changes have occurred or not.
   *
   */
  protected class RestartOperation extends ApplicationOperation {

    public RestartOperation(IModule[] modules) {
      super(modules);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.cloudfoundry.ide.eclipse.server.core.internal.client.
     * CloudFoundryServerBehaviour
     * .DeployAction#performDeployment(org.cloudfoundry
     * .ide.eclipse.internal.
     * server.core.client.CloudFoundryApplicationModule,
     * org.eclipse.core.runtime.IProgressMonitor)
     */
    protected void performDeployment(CloudFoundryApplicationModule appModule, IProgressMonitor monitor)
        throws CoreException {
      final Server server = (Server) getServer();
      final CloudFoundryApplicationModule cloudModule = appModule;

      try {
        cloudModule.setErrorStatus(null);

        final String deploymentName = cloudModule.getDeploymentInfo().getDeploymentName();

        server.setModuleState(modules, IServer.STATE_STARTING);

        if (deploymentName == null) {
          server.setModuleState(modules, IServer.STATE_STOPPED);

          throw CloudErrorUtil
              .toCoreException("Unable to start application. Missing application deployment name in application deployment information."); //$NON-NLS-1$
        }

        final ApplicationAction deploymentMode = getDeploymentConfiguration().getApplicationStartMode();
        if (deploymentMode != ApplicationAction.STOP) {
          // Start the application. Use a regular request rather than
          // a staging-aware request, as any staging errors should not
          // result in a reattempt, unlike other cases (e.g. get the
          // staging
          // logs or refreshing app instance stats after an app has
          // started).

          printlnToConsole(cloudModule, Messages.CONSOLE_PRE_STAGING_MESSAGE);

          CloudFoundryPlugin.getCallback().startApplicationConsole(getCloudFoundryServer(), cloudModule, 0,
              monitor);

          new BehaviourRequest<Void>("Starting application " + deploymentName) { //$NON-NLS-1$
            @Override
            protected Void doRun(final CloudFoundryOperations client, SubMonitor progress)
                throws CoreException {
              CloudFoundryPlugin.trace("Application " + deploymentName + " starting"); //$NON-NLS-1$ //$NON-NLS-2$

              switch (deploymentMode) {
              case DEBUG:
                // Only launch in Suspend mode
                client.debugApplication(deploymentName, DebugModeType.SUSPEND.getDebugMode());
                break;
              default:
                client.stopApplication(deploymentName);

                StartingInfo info = client.startApplication(deploymentName);
                if (info != null) {

                  cloudModule.setStartingInfo(info);

                  // Inform through callback that application
                  // has started
                  CloudFoundryPlugin.getCallback().applicationStarting(getCloudFoundryServer(),
                      cloudModule);
                }

                break;
              }
              return null;
            }
          }.run(monitor);

          // This should be staging aware, in order to reattempt on
          // staging related issues when checking if an app has
          // started or not
          new StagingAwareRequest<Void>("Waiting for application to start: }" + deploymentName) { //$NON-NLS-1$
            @Override
            protected Void doRun(final CloudFoundryOperations client, SubMonitor progress)
                throws CoreException {

              // Now verify that the application did start
              try {
                if (!waitForStart(client, deploymentName, progress)) {
                  server.setModuleState(modules, IServer.STATE_STOPPED);

                  throw new CoreException(new Status(IStatus.ERROR, CloudFoundryPlugin.PLUGIN_ID,
                      "Starting of " + cloudModule.getDeployedApplicationName() + " timed out")); //$NON-NLS-1$ //$NON-NLS-2$
                }
              }
              catch (InterruptedException e) {
                server.setModuleState(modules, IServer.STATE_STOPPED);
                throw new OperationCanceledException();
              }

              AbstractAppStateTracker curTracker = CloudFoundryPlugin.getAppStateTracker(getServer()
                  .getServerType().getId(), cloudModule);
              if (curTracker != null) {
                curTracker.setServer(getServer());
                curTracker.startTracking(cloudModule);
              }

              CloudFoundryPlugin.trace("Application " + deploymentName + " started"); //$NON-NLS-1$ //$NON-NLS-2$

              CloudFoundryPlugin.getCallback().applicationStarted(getCloudFoundryServer(), cloudModule);

              if (curTracker != null) {
                // Wait for application to be ready or getting
                // out of the starting state.
                boolean isAppStarting = true;
                while (isAppStarting && !progress.isCanceled()) {
                  if (curTracker.getApplicationState(cloudModule) == IServer.STATE_STARTING) {
                    try {
                      Thread.sleep(200);
                    }
                    catch (InterruptedException e) {
                      // Do nothing
                    }
                  }
                  else {
                    isAppStarting = false;
                  }
                }
                curTracker.stopTracking(cloudModule);
              }

              server.setModuleState(modules, IServer.STATE_STARTED);

              return null;
            }
          }.run(monitor);
        }
        else {
          // User has selected to deploy the app in STOP mode

          server.setModuleState(modules, IServer.STATE_STOPPED);
        }
      }
      catch (CoreException e) {
        appModule.setErrorStatus(e);
        server.setModulePublishState(modules, IServer.PUBLISH_STATE_UNKNOWN);
        throw e;
      }
    }

    @Override
    protected String getOperationName() {
      return Messages.CONSOLE_RESTARTING_APP;
    }

    @Override
    protected DeploymentConfiguration getDefaultDeploymentConfiguration() {
      return new DeploymentConfiguration(ApplicationAction.RESTART);
    }
  }

  abstract class FileRequest<T> extends StagingAwareRequest<T> {

    FileRequest() {
      super("Retrieving file"); //$NON-NLS-1$
    }
  }

  class StopApplicationOperation extends AbstractDeploymentOperation {

    private final IModule[] modules;

    protected StopApplicationOperation(IModule[] modules) {
      super(CloudFoundryServerBehaviour.this);
      this.modules = modules;
    }

    @Override
    protected void performOperation(IProgressMonitor monitor) throws CoreException {
      Server server = (Server) getServer();
      boolean succeeded = false;
      try {
        server.setModuleState(modules, IServer.STATE_STOPPING);

        CloudFoundryServer cloudServer = getCloudFoundryServer();
        final CloudFoundryApplicationModule cloudModule = cloudServer.getExistingCloudModule(modules[0]);

        if (cloudModule == null) {
          throw CloudErrorUtil.toCoreException("Unable to stop application as no cloud module found for: " //$NON-NLS-1$
              + modules[0].getName());
        }

        String stoppingApplicationMessage = NLS.bind(Messages.CONSOLE_STOPPING_APPLICATION,
            cloudModule.getDeployedApplicationName());

        clearAndPrintlnConsole(cloudModule, stoppingApplicationMessage);

        new BehaviourRequest<Void>(stoppingApplicationMessage) {
          @Override
          protected Void doRun(CloudFoundryOperations client, SubMonitor progress) throws CoreException {
            client.stopApplication(cloudModule.getDeployedApplicationName());
            return null;
          }
        }.run(monitor);

        server.setModuleState(modules, IServer.STATE_STOPPED);
        succeeded = true;

        printlnToConsole(cloudModule, Messages.CONSOLE_APP_STOPPED);
        CloudFoundryPlugin.getCallback().stopApplicationConsole(cloudModule, cloudServer);

        // If succeeded, stop all Caldecott tunnels if the app is
        // the
        // Caldecott app
        if (TunnelBehaviour.isCaldecottApp(cloudModule.getDeployedApplicationName())) {
          TunnelBehaviour handler = new TunnelBehaviour(cloudServer);
          handler.stopAndDeleteAllTunnels(monitor);
        }
      }
      finally {
        if (!succeeded) {
          server.setModuleState(modules, IServer.STATE_UNKNOWN);
        }
      }

    }

  }

  /**
   * Keep track on all the publish operation to be completed
   * <p/>
   * NS: Keeping in case a similar job monitor is needed in the future.
   * @author eyuen
   */
  static class PublishJobMonitor extends JobChangeAdapter {

    private List<Job> jobLst = new ArrayList<Job>();

    void init() {
      // Clean all existing jobs
      synchronized (jobLst) {
        jobLst.clear();
      }
    }

    @Override
    public void done(IJobChangeEvent event) {
      super.done(event);
      synchronized (jobLst) {
        jobLst.remove(event.getJob());
      }
    }

    void monitorJob(Job curJob) {
      curJob.addJobChangeListener(this);
      synchronized (jobLst) {
        jobLst.add(curJob);
      }
    }

    boolean isAllJobCompleted() {
      return jobLst.size() == 0;
    }

    /**
     * Wait for all job to be completed or the monitor is cancelled.
     * @param monitor
     */
    void waitForJobCompletion(IProgressMonitor monitor) {
      while ((monitor == null || !monitor.isCanceled()) && jobLst.size() > 0) {
        try {
          Thread.sleep(500);
        }
        catch (InterruptedException e) {
          // Do nothing
        }
      }
    }
  }

  abstract class BehaviourRequest<T> extends LocalServerRequest<T> {

    public BehaviourRequest(String label) {
      super(label);
    }

    @Override
    protected CloudFoundryOperations getClient(IProgressMonitor monitor) throws CoreException {
      return CloudFoundryServerBehaviour.this.getClient(monitor);
    }

    @Override
    protected CloudFoundryServer getCloudServer() throws CoreException {
      return CloudFoundryServerBehaviour.this.getCloudFoundryServer();
    }

  }
}
TOP

Related Classes of org.cloudfoundry.ide.eclipse.server.core.internal.client.CloudFoundryServerBehaviour$ApplicationOperation

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.