/* * 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 javax.annotation.Nonnull; import javax.annotation.concurrent.ThreadSafe; /** * Shared logic for all connection wrappers (PartnerConnection, MetadataConnection, ApexConnection). * * Be careful to maintain thread safety when subclassing this. All non-private methods must be synchronized. Only * subclasses may access the fields of this class. */ @ThreadSafe abstract class AbstractSalesforceConnection { /** * Semaphore to constraint concurrent calls. Make sure you use the semaphore every time you make a call to the * underlying port. The semaphore does not need to be guarded as it is inherently thread safe. */ private final CallSemaphore semaphore; /** * the ConnectionBundleImpl that this connection is a part of */ @SuppressWarnings("PackageVisibleField") final ConnectionBundleImpl connBundle; AbstractSalesforceConnection(@Nonnull CallSemaphore semaphore, @Nonnull ConnectionBundleImpl connBundle) { this.semaphore = semaphore; this.connBundle = connBundle; } /** * To be called by subclasses before each api call. This call specifically needs to NOT be synchronized since the * semaphore handles concurrency. * * @throws ApiException if interrupted while waiting */ final void acquireSemaphore() throws ApiException { try { this.semaphore.acquire(); } catch (InterruptedException e) { // we're not throwing a raw InterruptedException, so re-interrupt the thread for later detection Thread.currentThread().interrupt(); throw getApiExceptionWithCause("Interrupted while getting a permit", e); } } /** * Get a ApiException object without a stub ApiFault. It does not check for a INVALID_SESSION_ID fault code. * * @param message the exception message * @param cause the cause of the exception * * @return a call exception object */ @Nonnull ApiException getApiExceptionWithCause(@Nonnull String message, @Nonnull Throwable cause) { return ApiException.getNewWithCause(message, this.getUsername(), cause); } /** * To be called by subclasses after each api call. */ final void releaseSemaphore() { this.semaphore.release(); } @Nonnull final String getUsername() { return this.connBundle.getUsername(); } /** * This class should only be used by subclasses of the parent class. * * @param <Tin> the type of the input parameter to the api call * @param <Tout> the type of the output of the api call * @param <B> the type of the binding */ @SuppressWarnings("PackageVisibleInnerClass") abstract class ApiOperation<Tin, Tout, B> { /** * @param param the parameter to be supplied to the binding method call * * @return the binding's output * * @throws ApiException if execution fails */ final Tout execute(Tin param) throws ApiException { final ConfiguredBinding<B> configuredBinding = getBinding(); try { return executeImpl(configuredBinding, param); } finally { releaseBinding(configuredBinding.getBinding()); } } /** * @param configuredBinding the configuredBinding to use (will be released after this method returns) * @param param the input parameter * * @return the output data * * @throws ApiException if the call fails */ @Nonnull abstract Tout executeImpl(@Nonnull ConfiguredBinding<B> configuredBinding, @Nonnull Tin param) throws ApiException; @Nonnull abstract ConfiguredBinding<B> getBinding() throws ApiException; abstract void releaseBinding(@Nonnull B binding); } }