Java tutorial
/* * Copyright 2010. Team Lazer Beez (http://teamlazerbeez.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.teamlazerbeez.crm.sf.soap; import com.sun.xml.bind.api.JAXBRIContext; import com.sun.xml.ws.api.message.Header; import com.sun.xml.ws.api.message.Headers; import com.sun.xml.ws.developer.JAXWSProperties; import com.sun.xml.ws.developer.WSBindingProvider; import com.teamlazerbeez.crm.sf.core.Id; import com.teamlazerbeez.crm.sf.soap.jaxwsstub.apex.ApexPortType; import com.teamlazerbeez.crm.sf.soap.jaxwsstub.apex.DebuggingHeader; import com.teamlazerbeez.crm.sf.soap.jaxwsstub.apex.LogCategory; import com.teamlazerbeez.crm.sf.soap.jaxwsstub.apex.LogCategoryLevel; import com.teamlazerbeez.crm.sf.soap.jaxwsstub.apex.LogInfo; import com.teamlazerbeez.crm.sf.soap.jaxwsstub.apex.LogType; import com.teamlazerbeez.crm.sf.soap.jaxwsstub.metadata.MetadataPortType; import com.teamlazerbeez.crm.sf.soap.jaxwsstub.partner.CallOptions; import com.teamlazerbeez.crm.sf.soap.jaxwsstub.partner.InvalidIdFault_Exception; import com.teamlazerbeez.crm.sf.soap.jaxwsstub.partner.Login; import com.teamlazerbeez.crm.sf.soap.jaxwsstub.partner.LoginFault_Exception; import com.teamlazerbeez.crm.sf.soap.jaxwsstub.partner.LoginResponse; import com.teamlazerbeez.crm.sf.soap.jaxwsstub.partner.LoginResultType; import com.teamlazerbeez.crm.sf.soap.jaxwsstub.partner.SessionHeader; import com.teamlazerbeez.crm.sf.soap.jaxwsstub.partner.Soap; import com.teamlazerbeez.crm.sf.soap.jaxwsstub.partner.UnexpectedErrorFault_Exception; import org.joda.time.DateTime; import org.joda.time.Duration; import org.slf4j.ext.XLogger; import org.slf4j.ext.XLoggerFactory; import javax.annotation.Nonnull; import javax.annotation.concurrent.ThreadSafe; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.ws.BindingProvider; import javax.xml.ws.WebServiceException; import javax.xml.ws.handler.MessageContext; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Responsible for configuring the bindings for all wsdls. */ @ThreadSafe final class BindingConfigurer { /** * q the endpoint to make the initial connection to if the connection is for a sandbox. */ private static final String SANDBOX_INITIAL_ENDPOINT = "https://test.salesforce.com/services/Soap/u/" + ApiVersion.API_VERSION_STRING; /** * The endpoint used for regular organizations. The WSDL doesn't properly specify the endpoint, so we need to * override it. */ private static final String NORMAL_INITIAL_ENDPOINT = "https://login.salesforce.com/services/Soap/u/" + ApiVersion.API_VERSION_STRING; private static final XLogger logger = XLoggerFactory.getXLogger(BindingConfigurer.class); // jaxb context is thread safe private final JAXBRIContext partnerJaxbContext; private final JAXBRIContext metadataJaxbContext; private final JAXBRIContext apexJaxbContext; @Nonnull private final String partnerKey; private static final int EXPECTED_LOGIN_DURATION = 1000; BindingConfigurer(@Nonnull String partnerKey) { this.partnerKey = partnerKey; try { this.partnerJaxbContext = (JAXBRIContext) JAXBContext.newInstance(Soap.class.getPackage().getName()); this.metadataJaxbContext = (JAXBRIContext) JAXBContext .newInstance(MetadataPortType.class.getPackage().getName()); this.apexJaxbContext = (JAXBRIContext) JAXBContext .newInstance(ApexPortType.class.getPackage().getName()); } catch (JAXBException e) { throw new RuntimeException("Could not load JAXB context", e); } } /** * Set up a metadata binding for use. * * @param metadataBinding the binding to configure * @param bindingConfig the config data to apply */ void configureMetadataBinding(@Nonnull MetadataPortType metadataBinding, @Nonnull BindingConfig bindingConfig) { WSBindingProvider metadataWsBindingProvider = (WSBindingProvider) metadataBinding; this.configureRequestContextConnectionParams(metadataWsBindingProvider); // Set the session Id in the header com.teamlazerbeez.crm.sf.soap.jaxwsstub.metadata.SessionHeader sessionHeader = new com.teamlazerbeez.crm.sf.soap.jaxwsstub.metadata.SessionHeader(); sessionHeader.setSessionId(bindingConfig.getSessionId()); List<Header> headers = new ArrayList<Header>(); com.teamlazerbeez.crm.sf.soap.jaxwsstub.metadata.CallOptions metadataCallOpts = new com.teamlazerbeez.crm.sf.soap.jaxwsstub.metadata.CallOptions(); metadataCallOpts.setClient(this.partnerKey); headers.add(Headers.create(this.metadataJaxbContext, sessionHeader)); headers.add(Headers.create(this.metadataJaxbContext, metadataCallOpts)); metadataWsBindingProvider.setOutboundHeaders(headers); metadataWsBindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, bindingConfig.getMetadataServerUrl()); logger.exit("User " + bindingConfig.getUsername() + ", session id " + bindingConfig.getSessionId() + " on metadata server" + bindingConfig.getMetadataServerUrl()); } /** * Use the binding to get the config data for the org that the username and password points to.. * * @param username the username to log in with * @param password the password to log in with * @param binding the Soap binding to configure * @param callSemaphore the call semaphore to use when logging in * @param sandboxOrg true if this is a login to a sandbox org * * @return a result object containing a few useful bits of info discovered during the login process * * @throws ApiException if login fails */ @Nonnull BindingConfig loginAndGetBindingConfigData(@Nonnull String username, @Nonnull String password, @Nonnull Soap binding, @Nonnull CallSemaphore callSemaphore, boolean sandboxOrg) throws ApiException { Login loginParam = new Login(); loginParam.setPassword(password); loginParam.setUsername(username); // Get a BindingProvider ref to the port WSBindingProvider wsBindingProvider = (WSBindingProvider) binding; // reset initial endpoint if (sandboxOrg) { wsBindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, SANDBOX_INITIAL_ENDPOINT); } else { wsBindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, NORMAL_INITIAL_ENDPOINT); } logger.trace("Using initial endpoint: " + wsBindingProvider.getRequestContext().get(BindingProvider.ENDPOINT_ADDRESS_PROPERTY)); // reset headers to just be CallOptions CallOptions callOpts = new CallOptions(); callOpts.setClient(this.partnerKey); wsBindingProvider.setOutboundHeaders(Headers.create(this.partnerJaxbContext, callOpts)); this.configureRequestContextConnectionParams(wsBindingProvider); try { callSemaphore.acquire(); } catch (InterruptedException e) { // we're not throwing a raw InterruptedException, so re-interrupt the thread for later detection Thread.currentThread().interrupt(); throw ApiException.getNewWithCause("Interrupted while getting a call token to make the login call", username, e); } DateTime start = new DateTime(); LoginResponse response; try { response = binding.login(loginParam); } catch (InvalidIdFault_Exception e) { throw ApiException.getNewWithCauseAndStubApiFault("Invalid Id", username, e, e.getFaultInfo()); } catch (LoginFault_Exception e) { throw ApiException.getNewWithCauseAndStubApiFault("Bad credentials for user '" + username + "'", username, e, e.getFaultInfo()); } catch (UnexpectedErrorFault_Exception e) { throw ApiException.getNewWithCauseAndStubApiFault("Unexpected error", username, e, e.getFaultInfo()); } catch (WebServiceException e) { throw ApiException.getNewWithCause("Web Service exception", username, e); } finally { callSemaphore.release(); DateTime finish = new DateTime(); Duration duration = new Duration(start, finish); long actualDuration = duration.getMillis(); if (actualDuration > EXPECTED_LOGIN_DURATION) { logger.warn("Login took " + actualDuration + "ms, expected to take no more than " + EXPECTED_LOGIN_DURATION + "ms, user = " + username); } else { logger.trace("Login took " + actualDuration); } } LoginResultType loginResult = response.getResult(); logger.debug("User " + username + " using partner endpoint " + loginResult.getServerUrl()); // don't bother checking if the password is expired; wait for them to try and do something // with it... Id orgId = new Id(loginResult.getUserInfo().getOrganizationId()); String sessionId = loginResult.getSessionId(); return new BindingConfig(orgId, sessionId, loginResult.getServerUrl(), loginResult.getMetadataServerUrl(), username); } /** * @param binding the binding to configure * @param bindingConfig the config to apply to the binding */ void configurePartnerBinding(Soap binding, BindingConfig bindingConfig) { WSBindingProvider wsBindingProvider = (WSBindingProvider) binding; this.configureRequestContextConnectionParams(wsBindingProvider); // Set the endpoint URL wsBindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, bindingConfig.getPartnerServerUrl()); // Set the session Id in the header SessionHeader sessionHeader = new SessionHeader(); sessionHeader.setSessionId(bindingConfig.getSessionId()); // now that login is done, add both the session id header and the client id header // (callOpts) List<Header> headers = new ArrayList<Header>(); final CallOptions callOptions = new CallOptions(); callOptions.setClient(this.partnerKey); headers.add(Headers.create(this.partnerJaxbContext, sessionHeader)); headers.add(Headers.create(this.partnerJaxbContext, callOptions)); wsBindingProvider.setOutboundHeaders(headers); } void configureApexBinding(ApexPortType binding, BindingConfig bindingConfig) { WSBindingProvider apexWsBindingProvider = (WSBindingProvider) binding; this.configureRequestContextConnectionParams(apexWsBindingProvider); // Set the session Id in the header com.teamlazerbeez.crm.sf.soap.jaxwsstub.apex.SessionHeader sessionHeader = new com.teamlazerbeez.crm.sf.soap.jaxwsstub.apex.SessionHeader(); sessionHeader.setSessionId(bindingConfig.getSessionId()); List<Header> headers = new ArrayList<Header>(); com.teamlazerbeez.crm.sf.soap.jaxwsstub.apex.CallOptions apexCallOpts = new com.teamlazerbeez.crm.sf.soap.jaxwsstub.apex.CallOptions(); apexCallOpts.setClient(this.partnerKey); DebuggingHeader apexDebug = new DebuggingHeader(); apexDebug.setDebugLevel(LogType.PROFILING); for (LogCategory category : LogCategory.values()) { LogInfo logInfo = new LogInfo(); logInfo.setCategory(category); logInfo.setLevel(LogCategoryLevel.DEBUG); apexDebug.getCategories().add(logInfo); } headers.add(Headers.create(this.apexJaxbContext, sessionHeader)); headers.add(Headers.create(this.apexJaxbContext, apexCallOpts)); headers.add(Headers.create(this.apexJaxbContext, apexDebug)); apexWsBindingProvider.setOutboundHeaders(headers); apexWsBindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, bindingConfig.getApexServerUrl()); logger.exit("User " + bindingConfig.getUsername() + ", session id " + bindingConfig.getSessionId() + " on apex server" + bindingConfig.getApexServerUrl()); } /** * Set GZIP compression headers and timeouts * * @param wsBindingProvider the binding to adjust */ private void configureRequestContextConnectionParams(@Nonnull WSBindingProvider wsBindingProvider) { Map<String, List<String>> httpHeaders = new HashMap<String, List<String>>(); httpHeaders.put("Content-Encoding", Collections.singletonList("gzip")); httpHeaders.put("Accept-Encoding", Collections.singletonList("gzip")); wsBindingProvider.getRequestContext().put(MessageContext.HTTP_REQUEST_HEADERS, httpHeaders); // timeouts in millis int connectTimeout = 10 * 1000; // 10 min read timeout; count() call can take several minutes int readTimeout = 10 * 60 * 1000; wsBindingProvider.getRequestContext().put(JAXWSProperties.CONNECT_TIMEOUT, connectTimeout); wsBindingProvider.getRequestContext().put(JAXWSProperties.REQUEST_TIMEOUT, readTimeout); } }