com.salesforce.ide.core.services.ToolingDeployService.java Source code

Java tutorial

Introduction

Here is the source code for com.salesforce.ide.core.services.ToolingDeployService.java

Source

/*******************************************************************************
 * 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.services;

import java.util.List;

import org.apache.log4j.Logger;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IProgressMonitor;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
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.project.ForceProject;
import com.salesforce.ide.core.project.MarkerUtils;
import com.salesforce.ide.core.remote.ForceException;
import com.salesforce.ide.core.remote.ForceRemoteException;
import com.salesforce.ide.core.remote.ToolingStubExt;
import com.salesforce.ide.core.remote.tooling.ContainerAsyncRequestMessageHandler;
import com.salesforce.ide.core.remote.tooling.ContainerMemberFactory;
import com.salesforce.ide.core.remote.tooling.MetadataContainerFailureHandler;
import com.sforce.soap.tooling.ContainerAsyncRequest;
import com.sforce.soap.tooling.MetadataContainer;
import com.sforce.soap.tooling.QueryResult;
import com.sforce.soap.tooling.SObject;
import com.sforce.soap.tooling.SaveResult;

/**
 * A service for deploying via ContainerAsyncRequest through the Tooling API. This class takes care of creating the
 * necessary container member from the components in the workspace, when possible.
 * 
 * This service might be run for multiple projects concurrently. Ensure it is threadsafe by making it stateless.
 * 
 * @author nchen
 * 
 */
public class ToolingDeployService extends BaseService {

    private static final int POLL_INTERVAL = 1000;
    private static final Logger logger = Logger.getLogger(ToolingDeployService.class);

    /**
     * <p>
     * Deploys the list of components through the Tooling API. It will attempt to compile and save. If there are
     * compilation errors, it will notify the user.
     * </p>
     * <p>
     * We create a fresh MetadataContainer each time (but with the same name). This is cleaner in case we wind up in
     * some weird state with partially created ContainerMember. This is different from the Dev Console, because we have
     * a local file system when we can store intermediate state. We don't need to exploit ContainerMembers to store
     * intermediate state.
     * </p>
     * 
     * @param list
     *            List of components that CAN be deployed using Tooling API.
     * @param monitor
     *            Monitor to provide feedback to the user.
     */
    public void deploy(ForceProject project, ComponentList list, IProgressMonitor monitor) {
        //TODO: Optimize for the case of a single save, where we can just use the ContainerAsyncRequest without MetadataContainer
        try {
            clearSaveLocallyOnlyMarkers(list);

            ToolingStubExt stub = factoryLocator.getToolingFactory().getToolingStubExt(project);

            MetadataContainer container = new MetadataContainer();
            container.setName(constructProjectIdentifier(project));
            SaveResult[] containerResults = stub.create(new SObject[] { container });

            if (containerResults[0].isSuccess()) {
                String containerId = containerResults[0].getId();
                SObject[] classMembers = createContainerMembers(containerId, list);

                SaveResult[] classMemberResults = stub.create(classMembers);
                boolean allClassMembersCreatedSuccessfully = Iterables.all(Lists.newArrayList(classMemberResults),
                        new Predicate<SaveResult>() {

                            @Override
                            public boolean apply(SaveResult result) {
                                return result.isSuccess();
                            }
                        });

                if (allClassMembersCreatedSuccessfully) {
                    ContainerAsyncRequest request = new ContainerAsyncRequest();
                    request.setIsCheckOnly(false);
                    request.setMetadataContainerId(containerId);
                    SaveResult[] requestResults = stub.create(new SObject[] { request });

                    if (requestResults[0].isSuccess()) {
                        ContainerAsyncRequest onGoingRequest = pollForStatus(stub, requestResults, monitor);
                        handleContainerAsyncMessages(list, onGoingRequest);
                    } else {
                        handleContainerAsyncRequestCreationFailure(list, requestResults);
                    }
                } else {
                    handleClassMembersCreationFailure(list, classMemberResults);
                }

                // Clean up and delete the container member (this also deletes any ContainerMembers still referencing it)
                // If deletion fails, we will see a duplicate container error the next time we deploy and handle it there.
                stub.delete(new String[] { containerResults[0].getId() });

            } else {
                handleMetadataContainerCreationFailure(project, stub, list, containerResults);
            }

        } catch (ForceException e) {
            handleToolingDeployException(e);
        }

    }

    private void handleMetadataContainerCreationFailure(ForceProject project, ToolingStubExt stub,
            ComponentList list, SaveResult[] containerResults) {
        new MetadataContainerFailureHandler(project, stub).handleCreationFailure(containerResults);
        createSaveLocallyOnlyMarkers(list);
    }

    public void clearSaveLocallyOnlyMarkers(ComponentList list) {
        IResource[] resources = obtainListOfAffectedResources(list);
        MarkerUtils.getInstance().clearDirty(resources);
    }

    public void clearCompileErrorMarkers(ComponentList list) {
        IResource[] resources = obtainListOfAffectedResources(list);
        MarkerUtils.getInstance().clearCompileMarkers(resources);
    }

    public void createSaveLocallyOnlyMarkers(ComponentList list) {
        IResource[] resources = obtainListOfAffectedResources(list);
        MarkerUtils.getInstance().applyDirty(resources);
    }

    private IResource[] obtainListOfAffectedResources(ComponentList list) {
        IResource[] resources = Lists.transform(list, new Function<Component, IResource>() {

            @Override
            public IResource apply(Component cmp) {
                return cmp.getFileResource();
            }
        }).toArray(new IResource[0]);
        return resources;
    }

    ContainerAsyncRequest pollForStatus(ToolingStubExt stub, SaveResult[] requestResults, IProgressMonitor monitor)
            throws ForceRemoteException {
        String requestId = requestResults[0].getId();
        String soql = String.format(
                "SELECT Id, State, ErrorMsg, DeployDetails FROM ContainerAsyncRequest where id = '%s'", requestId);
        QueryResult queryResult = stub.query(soql);

        ContainerAsyncRequest onGoingRequest = (ContainerAsyncRequest) queryResult.getRecords()[0];

        return pollUntilUnqueuedOrCancelled(stub, monitor, soql, onGoingRequest);
    }

    ContainerAsyncRequest pollUntilUnqueuedOrCancelled(ToolingStubExt stub, IProgressMonitor monitor, String soql,
            ContainerAsyncRequest onGoingRequest) throws ForceRemoteException {
        QueryResult queryResult;
        int delayMultipler = 1;
        while (onGoingRequest.getState().equalsIgnoreCase("queued")) {
            try {
                Thread.sleep(POLL_INTERVAL * delayMultipler++);
                if (monitor.isCanceled()) { // The user has canceled the task
                    ContainerAsyncRequest abortedRequest = new ContainerAsyncRequest();
                    abortedRequest.setId(onGoingRequest.getId());
                    abortedRequest.setState("Aborted");
                    stub.update(new SObject[] { abortedRequest });
                    return abortedRequest;
                }
            } catch (InterruptedException e) {
                logger.debug("Exception while polling for ContainerAsyncRequest: ", e);
            }

            queryResult = stub.query(soql);
            onGoingRequest = (ContainerAsyncRequest) queryResult.getRecords()[0];

        }
        return onGoingRequest;
    }

    private void handleContainerAsyncMessages(ComponentList list, ContainerAsyncRequest onGoingRequest) {
        ContainerAsyncRequestMessageHandler handler = new ContainerAsyncRequestMessageHandler(list, onGoingRequest);
        handler.handle();
    }

    private void handleContainerAsyncRequestCreationFailure(ComponentList list, SaveResult[] requestResults) {
        assert requestResults.length == 1;
        logger.debug("Failed to create ContainerAsyncRequest for deployment: " + requestResults[0]);
        createSaveLocallyOnlyMarkers(list);
    }

    private void handleClassMembersCreationFailure(ComponentList list, SaveResult[] classMemberResults) {
        logger.debug("Failed to create ContainerMembers for deployment: ");
        for (SaveResult saveResult : classMemberResults) {
            logger.debug(saveResult);
        }
        createSaveLocallyOnlyMarkers(list);
    }

    private void handleToolingDeployException(ForceException e) {
        // TODO: Present useful information to the user in a dialog
        logger.warn(e);
    }

    /*
     * We would like to associate a unique identifer per project so that
     * i) we can easily track usage
     * ii) to prevent collision when we create the MetadataContainer since the name needs to be unique
     * Unfortunately, we cannot force users to upgrade their old projects to the new format so we act defensively here.
     */
    private String constructProjectIdentifier(ForceProject project) {
        String projectIdentifier = project.getProjectIdentifier();
        if (Utils.isNotEmpty(projectIdentifier)) {
            return projectIdentifier;
        }
        return "IDE - " + System.currentTimeMillis();
    }

    SObject[] createContainerMembers(final String containerId, final ComponentList list) {
        List<SObject> sObjects = Lists.newArrayList();

        for (Component component : list) {
            SObject toAdd = serviceLocator.getToolingService().componentDelegate(component, list,
                    new ContainerMemberFactory(containerId));

            if (toAdd != null)
                sObjects.add(toAdd);
        }

        return sObjects.toArray(new SObject[0]);

    }
}