/*******************************************************************************
* Copyright (c) 2014 Salesforce.com, inc..
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Salesforce.com, inc. - initial API and implementation
******************************************************************************/
package com.salesforce.ide.core.project;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import org.apache.log4j.Logger;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import com.salesforce.ide.core.factories.FactoryException;
import com.salesforce.ide.core.internal.context.ContainerDelegate;
import com.salesforce.ide.core.internal.controller.Controller;
import com.salesforce.ide.core.internal.utils.Constants;
import com.salesforce.ide.core.internal.utils.ForceExceptionUtils;
import com.salesforce.ide.core.internal.utils.Utils;
import com.salesforce.ide.core.model.Component;
import com.salesforce.ide.core.model.ComponentList;
import com.salesforce.ide.core.model.ProjectPackageList;
import com.salesforce.ide.core.remote.ForceConnectionException;
import com.salesforce.ide.core.remote.ForceRemoteException;
import com.salesforce.ide.core.remote.metadata.DeployResultExt;
import com.salesforce.ide.core.services.PackageDeployService;
import com.salesforce.ide.core.services.ServiceException;
import com.sforce.soap.metadata.DeployOptions;
/**
* Main controller for the build process.
*
* @author cwall
*
*/
public class BuilderController extends Controller {
private static final double FIRST_TOOLING_API_VERSION = 27.0;
private static final Logger logger = Logger.getLogger(BuilderController.class);
protected boolean bubbleExceptions = false;
protected boolean success = false;
public BuilderController() throws ForceProjectException {
super();
}
public BuilderPayload getBuilderPayloadInstance() {
return new BuilderPayload();
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public DeltaComponentSynchronizer getDeltaSynchronizer() {
return new DeltaComponentSynchronizer();
}
public boolean isBubbleExceptions() {
return bubbleExceptions;
}
public void setBubbleExceptions(boolean bubbleExceptions) {
this.bubbleExceptions = bubbleExceptions;
}
public void build(ComponentList saveComponentList, IProject project, IProgressMonitor monitor) throws Exception {
ForceProject forceProject =
ContainerDelegate.getInstance().getServiceLocator().getProjectService().getForceProject(project);
// If the user has opted for Tooling API and it's applicable, use the faster Tooling API route
if (forceProject.getPreferToolingDeployment() && isDeployableThroughToolingAPI(saveComponentList, forceProject)) {
buildThroughTooling(saveComponentList, project, forceProject, monitor);
} else { // Fallback to the Metadata API that supports all types
buildThroughMetadata(saveComponentList, project, forceProject, monitor);
}
}
private boolean isDeployableThroughToolingAPI(ComponentList saveComponentList, ForceProject forceProject) {
return saveComponentList.isDeployableThroughContainerAsyncRequest()
&& Float.parseFloat(forceProject.getEndpointApiVersion()) >= FIRST_TOOLING_API_VERSION;
}
private void buildThroughTooling(ComponentList saveComponentList, IProject project, ForceProject forceProject,
IProgressMonitor monitor) {
if (logger.isDebugEnabled()) {
logger.debug("*** B U I L D I N G (Tooling API) ***");
}
if (saveComponentList.isNotEmpty()) {
//TODO: Check for conflicts
ContainerDelegate.getInstance().getServiceLocator().getToolingDeployService()
.deploy(forceProject, saveComponentList, monitor);
}
if (logger.isDebugEnabled()) {
logger.debug("Finished building");
}
}
public void buildThroughMetadata(ComponentList saveComponentList, IProject project, ForceProject forceProject,
IProgressMonitor monitor) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("*** B U I L D I N G (Metadata Deploy) ***");
}
if (saveComponentList.isNotEmpty()) {
// set files to-be-saved in payload and filter out conflicts
BuilderPayload savePayload = getBuilderPayloadInstance();
savePayload.setProject(project);
try {
savePayload.loadPayload(saveComponentList, monitor);
handleSaves(savePayload, monitor);
} catch (Exception e) {
handleException(savePayload, "Unable to perform saves", e);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Finished building");
}
}
private void handleSaves(BuilderPayload savePayload, IProgressMonitor monitor) throws ForceRemoteException,
ForceConnectionException, ServiceException, InterruptedException, CoreException, IOException,
InvocationTargetException, Exception {
if (savePayload == null || savePayload.isEmpty() || !savePayload.hasSaveableComponents()) {
if (logger.isInfoEnabled()) {
logger.info("Save payload is null or empty. Nothing to save.");
}
return;
}
ProjectPackageList loadedProjectPackageList = savePayload.getLoadedProjectPackageList();
final PackageDeployService packageDeployService =
ContainerDelegate.getInstance().getServiceLocator().getPackageDeployService();
final DeployOptions deployOptions = makeDeployOptions(packageDeployService);
DeployResultExt deployResultHandler =
packageDeployService.deploy(loadedProjectPackageList, monitor, deployOptions);
ContainerDelegate.getInstance().getServiceLocator().getProjectService()
.handleDeployResult(loadedProjectPackageList, deployResultHandler, true, monitor);
}
protected DeployOptions makeDeployOptions(PackageDeployService packageDeployService)
throws ForceConnectionException {
final DeployOptions deployOptions = packageDeployService.makeDefaultDeployOptions(false);
deployOptions.setIgnoreWarnings(true);
return deployOptions;
}
// U T I L I T I E S
private void markAllDirty(IFile[] files, String msg) {
if (Utils.isNotEmpty(files)) {
for (IFile file : files) {
MarkerUtils markerUtils = MarkerUtils.getInstance();
markerUtils.clearAll(file);
markerUtils.applyDirty(file);
markerUtils.applySaveErrorMarker(file, 1, 1, 0, msg);
}
}
}
private void handleException(BuilderPayload payload, String message, Exception e) throws Exception {
logger.error("Unable to perform save on all files.", e);
markAllDirty(payload.getFiles(),
"Unable to perform save on all files: " + ForceExceptionUtils.getStrippedRootCauseMessage(e));
if (bubbleExceptions) {
throw e;
}
}
@Override
public void finish(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {}
@Override
public void init() {
}
@Override
public void dispose() {
}
public class DeltaComponentSynchronizer implements IResourceDeltaVisitor {
ComponentList saveComponentList = ContainerDelegate.getInstance().getFactoryLocator().getComponentFactory()
.getComponentListInstance();
public DeltaComponentSynchronizer() {}
@Override
public boolean visit(IResourceDelta delta) {
IResource resource = delta.getResource();
if (delta.getKind() != IResourceDelta.ADDED && delta.getKind() != IResourceDelta.CHANGED) {
if (logger.isDebugEnabled()) {
logger.debug("Build does not support '" + delta.getKind() + "' delta on '"
+ resource.getProjectRelativePath().toPortableString() + "'");
}
return true;
}
// Don't track changes anywhere except the "src" folder
if (resource.getType() == IResource.FOLDER
&& resource.getParent() instanceof IProject
&& !resource.getName().equals(Constants.SOURCE_FOLDER_NAME)
) {
if (logger.isDebugEnabled()) {
logger.debug("Resource '"
+ resource.getProjectRelativePath().toPortableString()
+ "' and children excluded from build.");
}
// Skip children
return false;
}
if (!ContainerDelegate.getInstance().getServiceLocator().getProjectService().isBuildableResource(resource)) {
if (logger.isDebugEnabled()) {
logger.debug("Resource '"
+ (resource.getType() == IResource.PROJECT ? resource.getName() : resource.getFullPath()
.toPortableString()) + "' is not a " + Constants.PLUGIN_NAME
+ " managed file. Excluding from build.");
}
return true;
}
IFile file = (IFile) resource;
try {
switch (delta.getKind()) {
case IResourceDelta.ADDED:
if (logger.isInfoEnabled()) {
logger.info("Resource '" + file.getFullPath().toPortableString() + "' was added to project '"
+ file.getProject().getName() + "'");
}
add(saveComponentList, file);
break;
case IResourceDelta.CHANGED:
if (logger.isInfoEnabled()) {
logger.info("Resource '" + file.getFullPath().toPortableString() + "' changed");
}
add(saveComponentList, file);
break;
case IResourceDelta.REMOVED:
if (logger.isInfoEnabled()) {
logger.info("Resource '" + file.getFullPath().toPortableString()
+ "' was removed from project '" + file.getProject().getName() + "'");
}
logger.warn("Deletes not handled by builder");
break;
default:
logger.warn("Delta '" + delta.getKind() + "' not handled for resource '"
+ file.getProjectRelativePath().toPortableString());
break;
}
} catch (FactoryException e) {
logger.error("Unable to handle file '" + file.getProjectRelativePath().toPortableString() + "': "
+ e.getMessage());
return false;
}
return true;
}
private void add(ComponentList componentList, IFile file) throws FactoryException {
Component component =
ContainerDelegate.getInstance().getFactoryLocator().getComponentFactory()
.getComponentFromFile(file);
if (component.isInstalled()) {
logger.warn("Skipping '" + component.getFullDisplayName() + "' - component is managed");
return;
}
componentList.add(component, true);
if (logger.isInfoEnabled()) {
logger.info("Component '" + component.getFullDisplayName() + "' addded to to-be-built list");
}
}
public ComponentList getSaveComponentList() {
return saveComponentList;
}
public boolean isSaveEmpty() {
return Utils.isEmpty(saveComponentList);
}
}
}