/*******************************************************************************
* 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.remote;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.salesforce.ide.core.factories.FactoryException;
import com.salesforce.ide.core.internal.context.ContainerDelegate;
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.sforce.soap.metadata.AsyncResult;
import com.sforce.soap.metadata.DebuggingHeader_element;
import com.sforce.soap.metadata.DeployOptions;
import com.sforce.soap.metadata.DeployResult;
import com.sforce.soap.metadata.DescribeMetadataObject;
import com.sforce.soap.metadata.DescribeMetadataResult;
import com.sforce.soap.metadata.FileProperties;
import com.sforce.soap.metadata.ListMetadataQuery;
import com.sforce.soap.metadata.LogInfo;
import com.sforce.soap.metadata.MetadataConnection;
import com.sforce.soap.metadata.RetrieveRequest;
import com.sforce.soap.metadata.RetrieveResult;
import com.sforce.ws.ConnectionException;
import com.sforce.ws.SoapFaultException;
public class MetadataStubExt {
private static final Logger logger = Logger.getLogger(MetadataStubExt.class);
private Connection connection;
private MetadataConnection metadataConnection;
//TODO: The connection will already have the api version which we're setting through spring. We can get rid of this.
private double apiVersion = 0.0;
public MetadataStubExt() {}
public Connection getConnection() {
return connection;
}
public MetadataConnection getMetadataConnection() {
return metadataConnection;
}
public void setMetadataConnection(MetadataConnection mc) {
metadataConnection = mc;
}
public int getReadTimeout() {
return metadataConnection.getConfig().getReadTimeout();
}
public double getApiVersion() {
return apiVersion;
}
public void setApiVersion(double apiVersion) {
this.apiVersion = apiVersion;
}
public void setTimeout(int milliSeconds) {
if (metadataConnection != null) {
metadataConnection.getConfig().setConnectionTimeout(milliSeconds);
metadataConnection.getConfig().setReadTimeout(milliSeconds);
}
if (logger.isDebugEnabled()) {
logger.debug("Timeout set to " + milliSeconds);
}
}
public void initializeMetadataConnection(Connection connection) throws ForceRemoteException {
if (connection == null) {
logger.warn("Unable to initialize metadata connection - connection is null");
return;
}
this.connection = connection;
if (logger.isDebugEnabled()) {
logger.debug("Preparing metadata stub for " + connection.getLogDisplay());
logger.debug("Using metadata server url: " + connection.getMetadataServerUrl());
}
try {
this.metadataConnection = new MetadataConnection(connection.getMetadataConnectorConfig());
} catch (ConnectionException e) {
if (logger.isDebugEnabled()) {
logger.debug("Could not set metadata connection with connection: " + connection.getLogDisplay());
}
ForceExceptionUtils.handleRemoteException(connection, e);
}
updateSessionId(connection.getSessionId());
metadataConnection.setCallOptions(connection.getApplication());
if (logger.isDebugEnabled()) {
logger.debug("Set client id on metadata stub '" + connection.getApplication() + "'");
}
}
public void setMetadataDebugHeader(LogInfo[] logInfos) {
if (metadataConnection == null) {
logger.warn("Unable to set debug level - metadata stub is null");
return;
}
//Does this do the same thing as below? if we even need it.
DebuggingHeader_element header = metadataConnection.getDebuggingHeader();
Map<String, LogInfo> logInfoMap = new HashMap<String, LogInfo>();
//Add old headers
if(header!=null){
for (LogInfo info : header.getCategories()) {
logInfoMap.put(info.getCategory().name(), info);
}
}
//Add new headers
if(logInfos!=null){
for (LogInfo info : logInfos) {
logInfoMap.put(info.getCategory().name(), info);
}
}
//Set the union of the old and new
metadataConnection.setDebuggingHeader(logInfoMap.values().toArray(new LogInfo[logInfoMap.size()]), null);
if (logger.isDebugEnabled()) {
logger.debug("Display current debug headers in MetadataConnection...");
for (LogInfo logInfo : logInfoMap.values()) {
logger.debug("Header '" + logInfo.toString() + "' is currently in MetadataConnection");
}
}
}
public String getServerName() {
if (connection == null) {
return Constants.EMPTY_STRING;
}
return connection.getMetadataServerUrl();
}
public void updateSessionId(String sessionId) {
if (metadataConnection != null) {
metadataConnection.setSessionHeader(sessionId);
if (logger.isDebugEnabled()) {
logger.debug("Set session id on metadata stub '" + sessionId + "'");
}
}
}
public AsyncResult retrieve(RetrieveRequest retrieveRequest) throws ForceRemoteException {
if (metadataConnection == null) {
throw new IllegalArgumentException("Metadata stub cannot be null");
}
if (logger.isDebugEnabled()) {
logger.debug("Timeout set to " + getReadTimeout() + " milliseconds");
}
AsyncResult asyncResult = null;
try {
asyncResult = metadataConnection.retrieve(retrieveRequest);
} catch (ConnectionException e) {
ForceExceptionUtils.throwTranslatedException(e, connection);
}
return asyncResult;
}
public RetrieveResult checkRetrieveStatus(String asyncProcessId) throws ForceRemoteException {
if (metadataConnection == null) {
throw new IllegalArgumentException("Metadata stub cannot be null");
}
RetrieveResult retrieveResult = null;
try {
retrieveResult = metadataConnection.checkRetrieveStatus(asyncProcessId);
} catch (ConnectionException e) {
ForceExceptionUtils.throwTranslatedException(e, connection);
}
return retrieveResult;
}
public DeployResult checkDeployStatus(String asyncProcessId) throws ForceRemoteException {
if (metadataConnection == null) {
throw new IllegalArgumentException("Metadata stub cannot be null");
}
DeployResult deployResult = null;
try {
deployResult = metadataConnection.checkDeployStatus(asyncProcessId, true);
} catch (ConnectionException e) {
ForceExceptionUtils.throwTranslatedException(e, connection);
}
return deployResult;
}
public AsyncResult[] checkStatus(String[] asyncProcessId) throws ForceRemoteException {
if (metadataConnection == null) {
throw new IllegalArgumentException("Metadata stub cannot be null");
}
AsyncResult[] asyncResult = null;
try {
asyncResult = metadataConnection.checkStatus(asyncProcessId);
} catch (ConnectionException e) {
ForceExceptionUtils.throwTranslatedException(e, connection);
}
return asyncResult;
}
public DescribeMetadataResult describeMetadata(double version) throws ForceRemoteException {
if (metadataConnection == null) {
throw new IllegalArgumentException("Metadata stub cannot be null");
}
if (logger.isInfoEnabled()) {
logger.info("Get describe metadata for version " + version);
}
if (logger.isDebugEnabled()) {
logger.debug("Timeout set to " + getReadTimeout() + " milliseconds");
}
DescribeMetadataResult describeMetadataResult = null;
try {
describeMetadataResult = metadataConnection.describeMetadata(version);
} catch (ConnectionException e) {
ForceExceptionUtils.throwTranslatedException(e, connection);
}
return describeMetadataResult;
}
public DescribeMetadataResult describeMetadata() throws ForceRemoteException {
return describeMetadata(apiVersion);
}
public FileProperties[] listMetadata(ListMetadataQuery[] allQueriesArray, IProgressMonitor monitor)
throws ForceRemoteException {
if (monitor==null) {
monitor = new NullProgressMonitor();
}
if (metadataConnection == null) {
throw new IllegalArgumentException("Metadata stub cannot be null");
}
if (logger.isDebugEnabled()) {
logger.debug("Querying metadata for FileProperties");
}
if (Utils.isEmpty(allQueriesArray)) {
return new FileProperties[] {};
}
// Remove unsupported components from queries.
Set<String> supportedComponents = getSupportedMetadataComponents();
ArrayList<ListMetadataQuery> allQueries = Lists.newArrayList(allQueriesArray);
Iterator<ListMetadataQuery> it = allQueries.iterator();
while (it.hasNext()) {
ListMetadataQuery query = it.next();
if (!supportedComponents.contains(query.getType())) {
it.remove();
}
}
//break request into 3 queries per api call (api constraint)
final int QUERIES_PER_CALL = 3;
List<FileProperties> filePropertiesList = new ArrayList<FileProperties>();
try {
for (List<ListMetadataQuery> queries : Lists.partition(allQueries, QUERIES_PER_CALL)) {
try {
filePropertiesList.addAll(getFileProperties(queries, monitor));
} catch (ConnectionException e) {
//Invalid type or timeout
if (ForceExceptionUtils.isReadTimeoutException(e) || e instanceof SoapFaultException) {
filePropertiesList.addAll(tryOneByOne(queries, monitor));
} else {
ForceExceptionUtils.throwTranslatedException(e, connection);
}
}
}
} catch (MonitorCanceledException e) {
// nothing to do, just return
}
return filePropertiesList.toArray(new FileProperties[filePropertiesList.size()]);
}
public Set<String> getSupportedMetadataComponents2() throws ForceRemoteException {
List<DescribeMetadataObject> metadataObjects = Lists.newArrayList(describeMetadata().getMetadataObjects());
Function<DescribeMetadataObject, String> metadataObjectToName = new Function<DescribeMetadataObject, String>() {
@Override
public String apply(DescribeMetadataObject metadataObject) { return metadataObject.getXmlName(); }
};
HashSet<String> supportedNames = new HashSet<String>();
supportedNames.addAll(Lists.transform(metadataObjects, metadataObjectToName));
return supportedNames;
}
public Set<String> getSupportedMetadataComponents() throws ForceRemoteException {
List<DescribeMetadataObject> metadataObjects = Lists.newArrayList(describeMetadata().getMetadataObjects());
HashSet<String> supportedNames = new HashSet<String>();
for (DescribeMetadataObject object : metadataObjects) {
String name = object.getXmlName();
supportedNames.add(name);
for (String child : object.getChildXmlNames()) {
supportedNames.add(child);
}
Component component = null;
try {
component = ContainerDelegate.getInstance().getFactoryLocator().getComponentFactory()
.getComponentByComponentType(name);
} catch (FactoryException e) {
//If an exception is thrown, it isn't supported
}
//Check the md folder in the bean
if (component != null && object.isInFolder()) {
supportedNames.add(component.getFolderNameIfFolderTypeMdComponent());
}
}
return supportedNames;
}
private List<FileProperties> tryOneByOne(List<ListMetadataQuery> queries,
IProgressMonitor monitor) throws MonitorCanceledException,
ForceRemoteException {
List<FileProperties> filePropertiesSubList = new ArrayList<FileProperties>();
for (List<ListMetadataQuery> listofOneQuery : Lists.partition(Lists.newArrayList(queries), 1)) {
try {
filePropertiesSubList.addAll(getFileProperties(listofOneQuery, monitor));
} catch (ConnectionException e) {
if (e instanceof SoapFaultException) {
logger.warn(e.getLocalizedMessage());
} else
if (ForceExceptionUtils.isReadTimeoutException(e)) {
logTimeout(listofOneQuery.get(0));
} else {
ForceExceptionUtils.throwTranslatedException(e, connection);
}
}
}
return filePropertiesSubList;
}
private void logTimeout(ListMetadataQuery query) {
logger.warn("Timeout while retrying to retrieve listMetadata for for component type "
+ query.getType()
+ (Utils.isNotEmpty(query.getFolder()) ? "[" + query.getFolder() + "]" : "")
+ " - will skip and continue");
}
/**
* Get FileProperties for all queries.
* @return a non-null array of FileProperties
* @throws MonitorCanceledException
* @throws ConnectionException
*/
private List<FileProperties> getFileProperties(List<ListMetadataQuery> queries,
IProgressMonitor monitor) throws MonitorCanceledException, ConnectionException {
checkMonitorIsCanceled(monitor);
logQueries(queries);
monitor.subTask(getMonitorMessage(queries));
FileProperties[] tmpFileProperties = null;
tmpFileProperties = metadataConnection.listMetadata(
queries.toArray(new ListMetadataQuery[queries.size()]),
getDefaultApiVersion()
);
List<FileProperties> properties = arrayToList(tmpFileProperties);
logger.debug("Got [" + properties.size() + "] file properties for component types");
checkMonitorIsCanceled(monitor);
monitor.worked(queries.size());
return properties;
}
private Double getDefaultApiVersion() {
return new Double(connection.getSalesforceEndpoints().getDefaultApiVersion());
}
private void checkMonitorIsCanceled(IProgressMonitor monitor) throws MonitorCanceledException {
if (monitor.isCanceled()) {
throw new MonitorCanceledException();
}
}
private ArrayList<FileProperties> arrayToList(
FileProperties[] tmpFileProperties) {
return tmpFileProperties == null ? Lists.<FileProperties>newArrayList() : Lists.newArrayList(tmpFileProperties);
}
private String getMonitorMessage(List<ListMetadataQuery> tmpListMetadataQueryList) {
final StringBuffer strBuff = new StringBuffer();
Set<String> componentTypes = new HashSet<String>();
for (Iterator<ListMetadataQuery> iterator = tmpListMetadataQueryList.iterator(); iterator.hasNext();) {
ListMetadataQuery listMetadataQuery = iterator.next();
if (Utils.isNotEmpty(listMetadataQuery.getFolder())) {
componentTypes.add(Utils.getPlural(listMetadataQuery.getType()));
} else {
componentTypes.add(listMetadataQuery.getType());
}
}
for (Iterator<String> iterator = componentTypes.iterator(); iterator.hasNext();) {
strBuff.append(iterator.next());
if (iterator.hasNext()) {
strBuff.append(", ");
}
}
return strBuff.toString() + "...";
}
private void logQueries(List<ListMetadataQuery> tmpListMetadataQueryList) {
if (logger.isDebugEnabled()) {
StringBuffer strBuff = new StringBuffer();
for (Iterator<ListMetadataQuery> iterator = tmpListMetadataQueryList.iterator(); iterator.hasNext();) {
ListMetadataQuery listMetadataQuery = iterator.next();
strBuff.append(listMetadataQuery.getType());
if (Utils.isNotEmpty(listMetadataQuery.getFolder())) {
strBuff.append(" [").append(listMetadataQuery.getFolder()).append("]");
}
if (iterator.hasNext()) {
strBuff.append(", ");
}
}
logger.debug("Calling listMetadata for component types\n: " + strBuff.toString());
}
}
public AsyncResult deploy(byte[] zipFile, DeployOptions deployOptions) throws ForceRemoteException {
if (metadataConnection == null) {
throw new IllegalArgumentException("Metadata stub cannot be null");
}
if (logger.isDebugEnabled()) {
logger.debug("Timeout set to " + getReadTimeout() + " milliseconds");
}
AsyncResult asyncResult = null;
try {
asyncResult = metadataConnection.deploy(zipFile, deployOptions);
} catch (ConnectionException e) {
ForceExceptionUtils.throwTranslatedException(e, connection);
}
return asyncResult;
}
public String getLogDisplay() {
return connection.getLogDisplay();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
long temp;
temp = Double.doubleToLongBits(apiVersion);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + ((connection == null) ? 0 : connection.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final MetadataStubExt other = (MetadataStubExt) obj;
if (Double.doubleToLongBits(apiVersion) != Double.doubleToLongBits(other.apiVersion))
return false;
if (connection == null) {
if (other.connection != null)
return false;
} else if (!connection.equals(other.connection))
return false;
return true;
}
/**
* Constructs a <code>String</code> with all attributes in name = value format.
*
* @return a <code>String</code> representation of this object.
*/
@Override
public String toString() {
final String TAB = ", ";
StringBuffer retValue = new StringBuffer();
retValue.append("MetadataStubExt ( ").append(super.toString()).append(TAB).append("connection = ").append(
connection != null ? connection.getLogDisplay() : "n/a").append(TAB).append(", apiVersion = ").append(
this.apiVersion).append(TAB).append(", timeout = ").append(
metadataConnection != null ? metadataConnection.getConfig().getReadTimeout() : "n/a").append(TAB).append(" )");
return retValue.toString();
}
public String toStringLite() {
final String TAB = ", ";
StringBuffer retValue = new StringBuffer();
retValue.append("apiVersion = ").append(this.apiVersion).append(TAB).append("timeout = ").append(
metadataConnection != null ? Utils.timeoutToSecs(metadataConnection.getConfig().getReadTimeout()) : "n/a");
return retValue.toString();
}
}