/* * Copyright © 2013. Palomino Labs (http://palominolabs.com) * * Licensed under 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. */ package com.palominolabs.crm.sf.soap; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; import com.palominolabs.crm.sf.core.Id; import com.palominolabs.crm.sf.soap.jaxwsstub.metadata.Metadata; import com.palominolabs.crm.sf.soap.jaxwsstub.metadata.MetadataPortType; import com.palominolabs.crm.sf.soap.jaxwsstub.metadata.UpdateMetadata; import org.joda.time.DateTime; import org.joda.time.Duration; import javax.annotation.Nonnull; import javax.annotation.concurrent.ThreadSafe; import javax.xml.ws.WebServiceException; import java.util.ArrayList; import java.util.List; /** * MetadataConnection does not have access to the same level of data as PartnerConnection, so it cannot perform * automatic INVALID_SESSION_ID recovery. */ @ThreadSafe final class MetadataConnectionImpl extends AbstractSalesforceConnection implements MetadataConnection { private final MetricRegistry metricRegistry; MetadataConnectionImpl(@Nonnull CallSemaphore semaphore, @Nonnull ConnectionBundleImpl bundle, MetricRegistry metricRegistry) { super(semaphore, bundle); this.metricRegistry = metricRegistry; } // Does not need to be synchronized since the actual api methods it calls are synchronized and there are no state // changes in this methd @Override @Nonnull public WaitForAsyncResult waitForAsyncResults(@Nonnull List<AsyncResult> results, long maxMillisToWait) throws ApiException, InterruptedException { DateTime startTime = new DateTime(); long millisWaited = 0; long nextWait = 1000; List<AsyncResult> latestAsyncResults = results; // flag to exit the loop at the right point boolean maxTimeHit = false; while (true) { List<Id> idsToCheck = new ArrayList<Id>(); for (AsyncResult result : latestAsyncResults) { if (result.isDone()) { // do not check this id continue; } idsToCheck.add(result.getId()); } if (idsToCheck.isEmpty() || maxTimeHit) { // we've got all the results, or we're done waiting DateTime endTime = new DateTime(); Duration elapsed = new Duration(startTime, endTime); return new WaitForAsyncResult(elapsed, latestAsyncResults); } latestAsyncResults = this.checkStatus(idsToCheck); if (millisWaited >= maxMillisToWait) { maxTimeHit = true; // do not wait, instead, loop through one more time to hit the timing processing block continue; } Thread.sleep(nextWait); millisWaited += nextWait; nextWait *= 2; // if the next wait will push us over the limit, trim the nextWait if (millisWaited + nextWait > maxMillisToWait) { nextWait = maxMillisToWait - millisWaited; } } } @Override @Nonnull public synchronized List<AsyncResult> checkStatus(@Nonnull List<Id> idsToCheck) throws ApiException { List<String> idStrList = new ArrayList<String>(); for (Id id : idsToCheck) { idStrList.add(id.toString()); } return convertStubAsyncResultList(new CheckStatusOp().execute(idStrList)); } @Override @Nonnull public synchronized List<AsyncResult> create(@Nonnull List<Metadata> metadataList) throws ApiException { return convertStubAsyncResultList(new CreateOp().execute(metadataList)); } @Override @Nonnull public synchronized List<AsyncResult> delete(@Nonnull List<Metadata> metadataList) throws ApiException { return convertStubAsyncResultList(new DeleteOp().execute(metadataList)); } @Override @Nonnull public synchronized List<AsyncResult> update(@Nonnull List<UpdateMetadata> metadataList) throws ApiException { return convertStubAsyncResultList(new UpdateOp().execute(metadataList)); } @Override @Nonnull public synchronized List<FileProperties> listMetadata(@Nonnull List<ListMetadataQuery> queries) throws ApiException { List<com.palominolabs.crm.sf.soap.jaxwsstub.metadata.ListMetadataQuery> stubList = new ArrayList<com.palominolabs.crm.sf.soap.jaxwsstub.metadata.ListMetadataQuery>(); for (ListMetadataQuery query : queries) { stubList.add(query.getStubObject()); } List<com.palominolabs.crm.sf.soap.jaxwsstub.metadata.FileProperties> stubResultList = new ListMetadataOp().execute(stubList); List<FileProperties> resultList = new ArrayList<FileProperties>(stubResultList.size()); for (com.palominolabs.crm.sf.soap.jaxwsstub.metadata.FileProperties stub : stubResultList) { resultList.add(new FileProperties(stub)); } return resultList; } @Override @Nonnull public synchronized AsyncResult retrieve(@Nonnull RetrieveRequest retrieveRequest) throws ApiException { return new AsyncResult(new RetrieveOp().execute(retrieveRequest.getStub())); } @Override @Nonnull public synchronized RetrieveResult getRetrieveResult(@Nonnull Id id) throws ApiException { return new RetrieveResult(new CheckRetrieveStatusOp().execute(id.toString())); } @Override @Nonnull public synchronized DescribeMetadataResult describeMetadata() throws ApiException { return this.describeMetadata(ApiVersion.API_VERSION_DOUBLE); } @Override @Nonnull public synchronized DescribeMetadataResult describeMetadata(double apiVersion) throws ApiException { final com.palominolabs.crm.sf.soap.jaxwsstub.metadata.DescribeMetadataResult describeMetadataResult = new DescribeMetadataOp().execute(apiVersion); return new DescribeMetadataResult(describeMetadataResult); } @Nonnull private static List<AsyncResult> convertStubAsyncResultList( @Nonnull List<com.palominolabs.crm.sf.soap.jaxwsstub.metadata.AsyncResult> stubAsyncResults) { List<AsyncResult> results = new ArrayList<AsyncResult>(); for (com.palominolabs.crm.sf.soap.jaxwsstub.metadata.AsyncResult stubAsyncResult : stubAsyncResults) { results.add(new AsyncResult(stubAsyncResult)); } return results; } private abstract class MetadataApiOperation<Tin, Tout> extends ApiOperation<Tin, Tout, MetadataPortType> { private final Timer timer = metricRegistry.timer(MetricRegistry.name(getClass(), "request")); @Nonnull @Override final Tout executeImpl(@Nonnull ConfiguredBinding<MetadataPortType> binding, @Nonnull Tin param) throws ApiException { Timer.Context context = timer.time(); try { MetadataConnectionImpl.this.acquireSemaphore(); try { return executeOp(binding.getBinding(), param); } finally { MetadataConnectionImpl.this.releaseSemaphore(); } } catch (WebServiceException e) { throw getApiExceptionWithCause("Call failed", e); } finally { context.stop(); } } /** * Should not be called directly. * * @param binding the binding to use * @param param input * * @return output */ @Nonnull abstract Tout executeOp(@Nonnull MetadataPortType binding, @Nonnull Tin param); @Override void releaseBinding(@Nonnull MetadataPortType binding) { connBundle.acceptReleasedMetadataBinding(binding); } @Nonnull @Override ConfiguredBinding<MetadataPortType> getBinding() throws ApiException { return connBundle.getMetadataBinding(); } } private class CheckStatusOp extends MetadataApiOperation<List<String>, List<com.palominolabs.crm.sf.soap.jaxwsstub.metadata.AsyncResult>> { @Nonnull @Override List<com.palominolabs.crm.sf.soap.jaxwsstub.metadata.AsyncResult> executeOp(@Nonnull MetadataPortType binding, @Nonnull List<String> param) { return binding.checkStatus(param); } } private class CreateOp extends MetadataApiOperation<List<Metadata>, List<com.palominolabs.crm.sf.soap.jaxwsstub.metadata.AsyncResult>> { @Nonnull @Override List<com.palominolabs.crm.sf.soap.jaxwsstub.metadata.AsyncResult> executeOp(@Nonnull MetadataPortType binding, @Nonnull List<Metadata> param) { return binding.create(param); } } private class DeleteOp extends MetadataApiOperation<List<Metadata>, List<com.palominolabs.crm.sf.soap.jaxwsstub.metadata.AsyncResult>> { @Nonnull @Override List<com.palominolabs.crm.sf.soap.jaxwsstub.metadata.AsyncResult> executeOp(@Nonnull MetadataPortType binding, @Nonnull List<Metadata> param) { return binding.delete(param); } } private class UpdateOp extends MetadataApiOperation<List<UpdateMetadata>, List<com.palominolabs.crm.sf.soap.jaxwsstub.metadata.AsyncResult>> { @Nonnull @Override List<com.palominolabs.crm.sf.soap.jaxwsstub.metadata.AsyncResult> executeOp(@Nonnull MetadataPortType binding, @Nonnull List<UpdateMetadata> param) { return binding.update(param); } } private class ListMetadataOp extends MetadataApiOperation<List<com.palominolabs.crm.sf.soap.jaxwsstub.metadata.ListMetadataQuery>, List<com.palominolabs.crm.sf.soap.jaxwsstub.metadata.FileProperties>> { @Nonnull @Override List<com.palominolabs.crm.sf.soap.jaxwsstub.metadata.FileProperties> executeOp( @Nonnull MetadataPortType binding, @Nonnull List<com.palominolabs.crm.sf.soap.jaxwsstub.metadata.ListMetadataQuery> param) { return binding.listMetadata(param, ApiVersion.API_VERSION_DOUBLE); } } private class RetrieveOp extends MetadataApiOperation<com.palominolabs.crm.sf.soap.jaxwsstub.metadata.RetrieveRequest, com.palominolabs.crm.sf.soap.jaxwsstub.metadata.AsyncResult> { @Nonnull @Override com.palominolabs.crm.sf.soap.jaxwsstub.metadata.AsyncResult executeOp(@Nonnull MetadataPortType binding, @Nonnull com.palominolabs.crm.sf.soap.jaxwsstub.metadata.RetrieveRequest param) { return binding.retrieve(param); } } private class CheckRetrieveStatusOp extends MetadataApiOperation<String, com.palominolabs.crm.sf.soap.jaxwsstub.metadata.RetrieveResult> { @Nonnull @Override com.palominolabs.crm.sf.soap.jaxwsstub.metadata.RetrieveResult executeOp(@Nonnull MetadataPortType binding, @Nonnull String param) { return binding.checkRetrieveStatus(param); } } private class DescribeMetadataOp extends MetadataApiOperation<Double, com.palominolabs.crm.sf.soap.jaxwsstub.metadata.DescribeMetadataResult> { @Nonnull @Override com.palominolabs.crm.sf.soap.jaxwsstub.metadata.DescribeMetadataResult executeOp( @Nonnull MetadataPortType binding, @Nonnull Double param) { return binding.describeMetadata(param); } } }