Java tutorial
/******************************************************************************* * 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]); } }