/*******************************************************************************
* Copyright (c) 2013, 2015 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.ui.internal;
import java.util.ArrayList;
import java.util.List;
import org.cloudfoundry.client.lib.domain.CloudService;
import org.cloudfoundry.ide.eclipse.server.core.ApplicationDeploymentInfo;
import org.cloudfoundry.ide.eclipse.server.core.internal.ApplicationUrlLookupService;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudErrorUtil;
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.application.ManifestParser;
import org.cloudfoundry.ide.eclipse.server.core.internal.client.CloudFoundryApplicationModule;
import org.cloudfoundry.ide.eclipse.server.core.internal.client.DeploymentConfiguration;
import org.cloudfoundry.ide.eclipse.server.core.internal.client.DeploymentInfoWorkingCopy;
import org.cloudfoundry.ide.eclipse.server.ui.internal.wizards.ApplicationWizardDelegate;
import org.cloudfoundry.ide.eclipse.server.ui.internal.wizards.ApplicationWizardRegistry;
import org.cloudfoundry.ide.eclipse.server.ui.internal.wizards.CloudFoundryApplicationWizard;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
/**
* Prepares an application for deployment. Application deployments are defined
* by a deployment info {@link ApplicationDeploymentInfo}. A check will be
* performed to see if there already exists a valid deployment info for the
* application about to be deployed.
* <p/>
* If the deployment descriptor is not valid, the handler will prompt the user
* for missing information via a deployment wizard.
* <p/>
* If a user was prompted by a wizard, an option also exists to overwrite the
* existing manifest file with the new deployment info changes.
*
*/
public class ApplicationDeploymentUIHandler {
/**
* Obtain a valid deployment info containing enough information to push an
* application, start it if necessary (starting an application is optional),
* and also optionally bind services to the application
* @param server where application should be pushed to.
* @param appModule pertaining to the application that needs to be pushed.
* @param monitor
* @throws CoreException if invalid deployment info.
* @throws OperationCanceledException if user canceled deployment.
* @return {@link DeploymentConfiguration} local deployment configuration
* for the app, or null if app should be deployed with default
* configuration.
*/
public DeploymentConfiguration prepareForDeployment(final CloudFoundryServer server,
final CloudFoundryApplicationModule appModule, final IProgressMonitor monitor) throws CoreException,
OperationCanceledException {
// Validate the existing deployment info. Do NOT save or make changes to
// the deployment info prior to this stage
// (for example, saving a working copy of the deployment info with
// default values), unless it was done so for a module that is being
// republished, as if the deployment info is valid,
// the wizard will not open. We want the deployment wizard to ALWAYS
// open when deploying an application for the first time (i.e the app
// will not have
// a deployment info set), even if we have to populate that deployment
// info with default values or values from the manifest file. The latter
// should only occur AFTER the handler decides to open the wizard.
if (!appModule.validateDeploymentInfo().isOK()) {
// Any application that can be pushed to a CF server
// MUST have a delegate
// which knows how to configure that application. If no
// delegate is found,
// the application is not currently deployable to a CF
// server. In that case, a delegate
// may be required to be registered for that application
// type.
final ApplicationWizardDelegate providerDelegate = ApplicationWizardRegistry.getWizardProvider(appModule
.getLocalModule());
if (providerDelegate == null) {
throw CloudErrorUtil.toCoreException("Failed to open application deployment wizard for: " //$NON-NLS-1$
+ appModule.getDeployedApplicationName()
+ " when attempting to push application to " //$NON-NLS-1$
+ server.getServer().getName()
+ ". No application provider found that corresponds to the application type: " //$NON-NLS-1$
+ appModule.getLocalModule().getModuleType().getId());
}
// Now parse the manifest file, if it exists, and load into a
// deployment info working copy.
// Do NOT save the working copy yet, as a user may cancel the
// operation from the wizard.
// THe working copy should only be saved by the wizard if a user
// clicks "OK".
DeploymentInfoWorkingCopy workingCopy = null;
try {
workingCopy = new ManifestParser(appModule, server).load(monitor);
}
catch (Throwable ce) {
// Some failure occurred reading the manifest file. Proceed
// anyway, to allow the user to manually enter deployment
// values.
CloudFoundryPlugin.logError(ce);
}
// A working copy of the deployment descriptor is needed in order to
// prepopulate the application deployment wizard.
if (workingCopy == null) {
workingCopy = appModule.resolveDeploymentInfoWorkingCopy(monitor);
}
// Get the old working copy in case during the deployment wizard,
// the app name changes
// Apps are looked up by app name in the manifest, therefore if the
// app name changed,
// the old entry in the manifest
ApplicationDeploymentInfo oldInfo = workingCopy.copy();
final boolean[] cancelled = { false };
final boolean[] writeToManifest = { false };
final IStatus[] status = { Status.OK_STATUS };
final DeploymentInfoWorkingCopy finWorkingCopy = workingCopy;
final DeploymentConfiguration[] configuration = new DeploymentConfiguration[1];
// Update the lookup
ApplicationUrlLookupService.update(server, monitor);
final List<CloudService> addedServices = new ArrayList<CloudService>();
Display.getDefault().syncExec(new Runnable() {
public void run() {
CloudFoundryApplicationWizard wizard = new CloudFoundryApplicationWizard(server, appModule,
finWorkingCopy, providerDelegate);
try {
WizardDialog dialog = new WizardDialog(PlatformUI.getWorkbench().getModalDialogShellProvider()
.getShell(), wizard);
int dialogueStatus = dialog.open();
if (dialogueStatus == Dialog.OK) {
// First add any new services to the server
List<CloudService> services = wizard.getCloudServicesToCreate();
if (services != null) {
addedServices.addAll(services);
}
writeToManifest[0] = wizard.persistManifestChanges();
configuration[0] = wizard.getDeploymentConfiguration();
}
else {
cancelled[0] = true;
}
}
catch (Throwable t) {
// Any error in the wizard should result in the module
// being deleted (i.e. cancelled)
cancelled[0] = true;
status[0] = CloudFoundryPlugin.getErrorStatus(t);
}
}
});
if (cancelled[0]) {
if (!status[0].isOK()) {
CloudFoundryPlugin.logError("Failed to deploy application due to: " + status[0].getMessage(), //$NON-NLS-1$
status[0].getException());
}
throw new OperationCanceledException();
}
else {
if (!addedServices.isEmpty()) {
try {
server.getBehaviour().operations().createServices(addedServices.toArray(new CloudService[0]))
.run(monitor);
}
catch (CoreException e) {
// Do not let service creation errors
// stop the application deployment
CloudFoundryPlugin.logError(e);
}
}
if (status[0].isOK()) {
status[0] = appModule.validateDeploymentInfo();
}
if (!status[0].isOK()) {
throw new CoreException(status[0]);
}
else if (writeToManifest[0]) {
try {
new ManifestParser(appModule, server).write(monitor, oldInfo);
}
catch (Throwable ce) {
// Do not let this error propagate, as failing to write
// to the manifest should not stop the app's deployment
CloudFoundryPlugin.logError(ce);
}
}
return configuration[0];
}
}
return null;
}
}