/*******************************************************************************
* 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
********************************************************************************/
package org.cloudfoundry.ide.eclipse.server.core.internal.client;
import org.cloudfoundry.client.lib.StartingInfo;
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.InstancesInfo;
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.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.application.ApplicationRegistry;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.wst.server.core.IModule;
import org.eclipse.wst.server.core.IServer;
import org.eclipse.wst.server.core.internal.ExternalModule;
/**
*
* Representation of an application that either already exists in a Cloud
* Foundry server, or is about to be pushed to a server, and contains additional
* properties local to the plugin framework not found in a
* {@link CloudApplication}. It contains additional Cloud Foundry information
* like a deployment information ( {@link ApplicationDeploymentInfo} ),
* application stats, instances, and staging that is not available from the
* local WST {@link IModule}. A cloud foundry application module need not
* necessarily indicate that the application exists and is deployed in a CF
* server. Modules are also created by the framework PRIOR to deploying an app.
* If the module has a corresponding {@link CloudApplication}, it means that the
* module does indeed represent an actual deployed app in a CF server. In
* addition:
* <p/>
* 1. A Cloud module can be external, meaning that the deployed application does
* not have an accessible workspace project. If this is the case, generally the
* module will also have a mapped {@link CloudApplication}, since only
* applications that have already been deployed can ever be external. Note
* although all external apps are apps that are deployed in a Cloud server, not
* all deployed apps are external. "External" is not the only indication of
* deployment, rather it is meant to indicate whether the application is linked
* to a local, accessible workspace project.
* <p/>
* 2. A Cloud module can also be mapped to a local workspace project via a local
* {@link IModule}, in which case it would not be classified as external. This
* does NOT mean that the application is not deployed. The application may be
* deployed in a CF server, but also have a link to a local workspace project.
* An application may also have a link to a local workspace project, but NOT yet
* be deployed (so it wouldn't have a mapped {@link CloudApplication}).
*
* <p/>
* The application name of this CF-aware module may differ from the module name
* of the local WST {@link IModule}. The reason is that the module name of the
* local WST {@link IModule} is typically the associated workspace project, if
* the project is accessible, while the application name in the CF Application
* Module is the user-specified CF app name, which may be different.
* <p/>
* To obtain the local WST module name, use {@link #getName()} or get it through
* {@link #getLocalModule()}, although the latter may be null if no IModule
* mapping has been created and linked by the framework. Local names may be used
* for obtaining workspace resources, like for example the application's
* corresponding workspace project.
* <p/>
* To obtain the deployed application name, use
* {@link #getDeployedApplicationName()}.
* <p/>
* The application module may be shared by multiple threads, therefore changes
* should be synchronised at the very least.
* <p/>
* The app module also contains a deployment information (
* {@link ApplicationDeploymentInfo} ), which describes deployment properties of
* the application (e.g., URLs, memory settings, etc..), as well as services
* that are bound, or will be bound, to the application.
* <p/>
* If the application has already been deployed (i.e. has a corresponding
* {@link CloudApplication}), the deployment information is kept in synch any
* time the module mapping to a {@link CloudApplication} is changed.
*
* 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 Nieraj Singh
* @author Christian Dupuis
* @author Terry Denney
* @author Leo Dos Santos
* @author Steffen Pingel
*/
@SuppressWarnings("restriction")
public class CloudFoundryApplicationModule extends ExternalModule implements ICloudFoundryApplicationModule {
public static String APPLICATION_STATE_DEPLOYABLE = Messages.CloudFoundryApplicationModule_STATE_DEPLOYABLE;
public static String APPLICATION_STATE_DEPLOYED = Messages.CloudFoundryApplicationModule_STATE_DEPLOYED;
public static String APPLICATION_STATE_UPLOADING = Messages.CloudFoundryApplicationModule_STATE_UPLOADING;
public static String DEPLOYMENT_STATE_LAUNCHED = Messages.CloudFoundryApplicationModule_STATE_LAUNCHED;
public static String DEPLOYMENT_STATE_LAUNCHING = Messages.CloudFoundryApplicationModule_STATE_LAUNCHING;
public static String DEPLOYMENT_STATE_STARTING_SERVICES = Messages.CloudFoundryApplicationModule_STATE_STARTING_SERVICES;
public static String DEPLOYMENT_STATE_STOPPED = Messages.CloudFoundryApplicationModule_STATE_STOPPED;
public static String DEPLOYMENT_STATE_STOPPING = Messages.CloudFoundryApplicationModule_STATE_STOPPING;
public static String DEPLOYMENT_STATE_WAITING_TO_LAUNCH = Messages.CloudFoundryApplicationModule_STATE_WAITING_TO_LAUNCH;
private static final String MODULE_ID = "org.cloudfoundry.ide.eclipse.server.core.CloudFoundryApplicationModule"; //$NON-NLS-1$
private static final String MODULE_VERSION = "1.0"; //$NON-NLS-1$
private CloudApplication application;
private String deployedAppName;
private ApplicationStats applicationStats;
private InstancesInfo instancesInfo;
private ApplicationDeploymentInfo deploymentInfo;
private StartingInfo startingInfo;
private IModule localModule;
private final IServer server;
private CoreException error;
/**
* Creates a cloud module that has a corresponding local module. This should
* be used if there is an accessible workspace project for the deployed app
* (the presence of an IModule would indicate a possible accessible
* workspace resource for the application).
* @param module local module from the WST server. Must not be null.
* @param deployedApplicationName name of the deployed application. It may
* not match the local workspace project name, as users are allowed to
* specify a different deployment name when pushing an application. Must not
* be null
* @param server. Must not be null.
*/
public CloudFoundryApplicationModule(IModule module, String deployedApplicationName, IServer server) {
this(module, deployedApplicationName, module.getName(), server);
}
/**
* Creates an external cloud module (a cloud module that corresponds to a
* deployed application with no accessible workspace project).
* @param deployedApplicationName. Must not be null.
* @param server. Must not be null.
*/
public CloudFoundryApplicationModule(String deployedApplicationName, IServer server) {
this(null, deployedApplicationName, deployedApplicationName, server);
}
protected CloudFoundryApplicationModule(IModule module, String deployedApplicationName, String localName,
IServer server) {
super(localName, localName, MODULE_ID, MODULE_VERSION, null);
Assert.isNotNull(deployedApplicationName);
Assert.isNotNull(localName);
Assert.isNotNull(server);
this.localModule = (module != null) ? module : this;
this.server = server;
setDeployedApplicationName(deployedApplicationName);
CloudFoundryPlugin.trace("Created ApplicationModule " + deployedApplicationName + " for module " + module); //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* A mapping to a cloud application representing a deployed application. A
* non-null cloud application means that the application is already deployed
* and exists in the CF server.
* <p/>
* If cloud application is null, it means the application module has not yet
* been deployed, or there was an error mapping the local application module
* with the actual deployed application (e.g. a connection error when trying
* to refresh the list of currently deployed applications).
* @return the actual cloud application obtained from the CF client library
* indicating a deployed application. It may be null.
*/
public CloudApplication getApplication() {
return application;
}
/**
* The deployed application name. This may be different from the IModule
* name which is typically the project name (if accessible). Therefore to
* get the name of the actual app, always use this API. To get the local
* module name use {@link #getName()}, which matches the local workspace
* project, or get it through the IModule itself {@link #getLocalModule()}.
* @see IModule#getName()
* @see #getLocalModule()
*/
public synchronized String getDeployedApplicationName() {
return deployedAppName;
}
public ApplicationStats getApplicationStats() {
return applicationStats;
}
public StartingInfo getStartingInfo() {
return startingInfo;
}
public void setStartingInfo(StartingInfo startingInfo) {
this.startingInfo = startingInfo;
}
public InstancesInfo getInstancesInfo() {
return instancesInfo;
}
public synchronized int getInstanceCount() {
if (application != null) {
return application.getInstances();
}
return 0;
}
/**
* Returns a copy of the application's deployment info describing deployment
* properties for the application like the application's memory settings,
* mapped URLs and bound services.
* <p/>
* Changes to the copy will have no effect. To make changes to the
* deployment information, request a working copy, and save it. See
* {@link #getDeploymentInfoWorkingCopy()}
*
* <p/>
* If null, it means that the application is not currently deployed in the
* server, or the plugin has not yet determined if the application is
* deployed.
* <p/>
* If not null, it does NOT necessarily mean the application is deployed, as
* the application may be in the process of being deployed and will
* therefore have a deployment information.
* @return a copy of the application's deployment information. Changes to
* the copy will have no effect.
*/
public synchronized ApplicationDeploymentInfo getDeploymentInfo() {
return deploymentInfo != null ? deploymentInfo.copy() : null;
}
/**
* Creates a working copy of the current deployment information. If the
* application does not have a current deployment information, a working
* copy will be generated from the app's deployment default values. A new
* copy is always returned. No changes take effect in the app modules'
* deployment info unless the working copy is saved.
* <p/>
* @return a new working copy with either existing deployment information,
* or default deployment information, if an deployment information does not
* exist.
*/
public synchronized DeploymentInfoWorkingCopy resolveDeploymentInfoWorkingCopy(IProgressMonitor monitor)
throws CoreException {
DeploymentInfoWorkingCopy wc = new ModuleDeploymentInfoWorkingCopy(this);
wc.fill(monitor);
return wc;
}
/**
*
* @see AbstractApplicationDelegate#validateDeploymentInfo(ApplicationDeploymentInfo)
* @return OK status if deployment information is complete and valid. Error
* if failed to validate, or is invalid (i.e. it is missing information).
*/
public synchronized IStatus validateDeploymentInfo() {
AbstractApplicationDelegate delegate = ApplicationRegistry.getApplicationDelegate(getLocalModule());
if (delegate == null) {
return AbstractApplicationDelegate.basicValidateDeploymentInfo(deploymentInfo);
}
return delegate.validateDeploymentInfo(deploymentInfo);
}
/**
*
* Returns the local WST module mapping. If present (not null), it most
* likely means that there is an accessible Eclipse workspace project for
* the application. If null, it means the application is external, which
* indicates that it is deployed in a CF server but does not have an
* accessible workspace project.
*
* @return local WST module. May be null if the application is external.
*/
public IModule getLocalModule() {
return localModule;
}
public int getPublishState() {
// if (isExternal()) {
return IServer.PUBLISH_STATE_NONE;
// }
// return IServer.PUBLISH_STATE_UNKNOWN;
}
public String getServerTypeId() {
return server.getServerType().getId();
}
/**
*
* @return {@link IServer} state
*/
public synchronized int getState() {
if (application != null) {
AppState state = application.getState();
switch (state) {
case STARTED:
return IServer.STATE_STARTED;
case UPDATING:
return IServer.STATE_STARTING;
case STOPPED:
return IServer.STATE_STOPPED;
}
}
return IServer.STATE_UNKNOWN;
}
public boolean isExternal() {
return localModule == this;
}
public synchronized void setErrorStatus(CoreException error) {
this.error = error;
}
public synchronized String getErrorMessage() {
if (error == null) {
return null;
}
return error.getMessage();
}
public synchronized void setApplicationStats(ApplicationStats applicationStats) {
this.applicationStats = applicationStats;
}
public synchronized void setInstancesInfo(InstancesInfo instancesInfo) {
this.instancesInfo = instancesInfo;
}
/**
* Maps the application module to an actual deployed application in a CF
* server. It replaces any existing deployment info with one generated from
* the cloud application. The existing deployment descriptor remains
* unchanged if removing the cloud application mapping (i.e. setting to
* null)
*
* @param cloudApplication the actual deployed application in a CF server.
* @throws CoreException if failure occurred while setting a cloud
* application, or the deployment info is currently being modified by some
* other component.
*/
public synchronized void setCloudApplication(CloudApplication cloudApplication) {
this.application = cloudApplication;
if (application != null) {
// Update the deployment info so that it reflects the actual
// deployed
// application. Note that Eclipse-specific properties are retained
// from
// existing deployment infos.
// Only the actual deployed app properties (e.g. name, services,
// URLs)
// are updated from the cloud application
ApplicationDeploymentInfo cloudApplicationInfo = resolveDeployedApplicationInformation();
if (cloudApplicationInfo != null) {
internalSetDeploymentInfo(cloudApplicationInfo);
}
}
}
/**
*
* @return true if the application is published to the Cloud Foundry server.
* False otherwise.
*/
public synchronized boolean isDeployed() {
return getApplication() != null && getState() != IServer.STATE_UNKNOWN;
}
/**
* Sets a deployment information for the application. Note that if the
* application is already deployed (i.e. a {@link CloudApplication} mapping
* exists for this module), this will overwrite the deployment information
* for the {@link CloudApplication}.
* @param lastDeploymentInfo the latest deployment of the application. IF
* the application name in the latest deployment has changed, the current
* module name will also be updated. If setting null (e.g. application is
* being deleted), the current module name will remain unchanged.
*/
private void internalSetDeploymentInfo(ApplicationDeploymentInfo deploymentInfo) {
this.deploymentInfo = deploymentInfo;
// Note that last Deployment info may be null (e.g. when deleting an
// application). Only update the appliation ID if setting a new last
// deployment info, since
// the module should match the application properties listed in the
// latest deployment, including any app name changes.
if (deploymentInfo != null && deploymentInfo.getDeploymentName() != null) {
setDeployedApplicationName(deploymentInfo.getDeploymentName());
}
}
/*
*
* Internal helper methods. Non-synchronized
*/
/**
* Resolve deployment information from values in the corresponding deployed
* application ( {@link CloudApplication} ). If the application is not yet
* deployed (i.e., cloud application is null), null is returned.
*
* @param appModule application currently deployed in CF server
* @param cloudServer server where app is deployed
* @return a new copy of the deployment info for the deployed app, or null
* if the cloud application is null
*/
protected ApplicationDeploymentInfo resolveDeployedApplicationInformation() {
if (application == null) {
return null;
}
AbstractApplicationDelegate delegate = ApplicationRegistry.getApplicationDelegate(getLocalModule());
ApplicationDeploymentInfo info = null;
CloudFoundryServer cloudServer = getCloudFoundryServer();
if (delegate != null) {
info = delegate.resolveApplicationDeploymentInfo(this, cloudServer);
}
// If no info has been resolved yet, use a default parser
if (info == null) {
info = AbstractApplicationDelegate.parseApplicationDeploymentInfo(application);
}
return info;
}
/**
* Application name must not be null. This is the deployed application name.
* @param applicationName most not be null
*/
protected void setDeployedApplicationName(String applicationName) {
Assert.isNotNull(applicationName);
if (!applicationName.equals(this.deployedAppName)) {
this.deployedAppName = applicationName;
if (localModule != null) {
CloudFoundryServer cloudServer = getCloudFoundryServer();
// Since the deployment name changed, update the local module ->
// deployed module cache in the server
cloudServer.updateApplicationModule(this);
}
}
}
/**
* Returns a default deployment information, with basic information to
* deploy or start/restart an application. It is not guaranteed to be
* complete or valid, as in some cases missing information is acceptable
* since additional deployment steps may involve prompting for the missing
* values.
* <p/>
* Never null. At the very basic, it will set a simple default deployment
* information with just the application name and memory setting.
* @return non-null default deployment info. This default information is
* also set in the module as the module's current deployment information.
*/
protected ApplicationDeploymentInfo getDefaultDeploymentInfo(IProgressMonitor monitor) throws CoreException {
AbstractApplicationDelegate delegate = ApplicationRegistry.getApplicationDelegate(getLocalModule());
ApplicationDeploymentInfo defaultInfo = null;
if (delegate != null) {
defaultInfo = delegate.getDefaultApplicationDeploymentInfo(this, getCloudFoundryServer(), monitor);
}
if (defaultInfo == null) {
defaultInfo = createGeneralDefaultInfo();
}
return defaultInfo;
}
/**
* Creates a general deployment info that should be applicable to any
* application type. It will have an app name as well as memory setting.
* @return Non-null general deployment info with basic information for
* application deployment.
*/
protected ApplicationDeploymentInfo createGeneralDefaultInfo() {
ApplicationDeploymentInfo info = new ApplicationDeploymentInfo(getDeployedApplicationName());
info.setMemory(CloudUtil.DEFAULT_MEMORY);
return info;
}
protected CloudFoundryServer getCloudFoundryServer() {
return (CloudFoundryServer) server.loadAdapter(CloudFoundryServer.class, null);
}
/**
* Should not be instantiated outside of a Cloud Module, as it is coupled
* with the implementation of the module.
*/
protected class ModuleDeploymentInfoWorkingCopy extends DeploymentInfoWorkingCopy {
protected ModuleDeploymentInfoWorkingCopy(CloudFoundryApplicationModule appModule) {
super(appModule);
}
@Override
public void save() {
synchronized (appModule) {
// Set the working copy as a regular deployment info, as to not
// keeping
// a reference to the working copy
ApplicationDeploymentInfo info = new ApplicationDeploymentInfo(getDeployedApplicationName());
info.setInfo(this);
appModule.internalSetDeploymentInfo(info);
}
}
}
}