/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.camel.component.salesforce.internal.client; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.Arrays; import java.util.Collections; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.transform.stream.StreamSource; import org.apache.camel.component.salesforce.SalesforceHttpClient; import org.apache.camel.component.salesforce.api.SalesforceException; import org.apache.camel.component.salesforce.api.dto.RestError; import org.apache.camel.component.salesforce.api.dto.bulk.BatchInfo; import org.apache.camel.component.salesforce.api.dto.bulk.BatchInfoList; import org.apache.camel.component.salesforce.api.dto.bulk.ContentType; import org.apache.camel.component.salesforce.api.dto.bulk.Error; import org.apache.camel.component.salesforce.api.dto.bulk.JobInfo; import org.apache.camel.component.salesforce.api.dto.bulk.JobStateEnum; import org.apache.camel.component.salesforce.api.dto.bulk.ObjectFactory; import org.apache.camel.component.salesforce.api.dto.bulk.QueryResultList; import org.apache.camel.component.salesforce.internal.SalesforceSession; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.util.BytesContentProvider; import org.eclipse.jetty.client.util.InputStreamContentProvider; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.util.StringUtil; public class DefaultBulkApiClient extends AbstractClientBase implements BulkApiClient { private static final String TOKEN_HEADER = "X-SFDC-Session"; private static final ContentType DEFAULT_ACCEPT_TYPE = ContentType.XML; private JAXBContext context; private ObjectFactory objectFactory; public DefaultBulkApiClient(String version, SalesforceSession session, SalesforceHttpClient httpClient) throws SalesforceException { super(version, session, httpClient); try { context = JAXBContext.newInstance(JobInfo.class.getPackage().getName(), getClass().getClassLoader()); } catch (JAXBException e) { String msg = "Error loading Bulk API DTOs: " + e.getMessage(); throw new IllegalArgumentException(msg, e); } this.objectFactory = new ObjectFactory(); } @Override public void createJob(JobInfo request, final JobInfoResponseCallback callback) { // clear system fields if set sanitizeJobRequest(request); final Request post = getRequest(HttpMethod.POST, jobUrl(null)); try { marshalRequest(objectFactory.createJobInfo(request), post, APPLICATION_XML_UTF8); } catch (SalesforceException e) { callback.onResponse(null, e); return; } // make the call and parse the result in callback doHttpRequest(post, new ClientResponseCallback() { @Override public void onResponse(InputStream response, SalesforceException ex) { JobInfo value = null; if (response != null) { try { value = unmarshalResponse(response, post, JobInfo.class); } catch (SalesforceException e) { ex = e; } } callback.onResponse(value, ex); } }); } // reset read only fields private void sanitizeJobRequest(JobInfo request) { request.setApexProcessingTime(null); request.setApiActiveProcessingTime(null); request.setApiVersion(null); request.setCreatedById(null); request.setCreatedDate(null); request.setId(null); request.setNumberBatchesCompleted(null); request.setNumberBatchesFailed(null); request.setNumberBatchesInProgress(null); request.setNumberBatchesQueued(null); request.setNumberBatchesTotal(null); request.setNumberRecordsFailed(null); request.setNumberRecordsProcessed(null); request.setNumberRetries(null); request.setState(null); request.setSystemModstamp(null); request.setSystemModstamp(null); } @Override public void getJob(String jobId, final JobInfoResponseCallback callback) { final Request get = getRequest(HttpMethod.GET, jobUrl(jobId)); // make the call and parse the result doHttpRequest(get, new ClientResponseCallback() { @Override public void onResponse(InputStream response, SalesforceException ex) { JobInfo value = null; try { value = unmarshalResponse(response, get, JobInfo.class); } catch (SalesforceException e) { ex = e; } callback.onResponse(value, ex); } }); } @Override public void closeJob(String jobId, final JobInfoResponseCallback callback) { final JobInfo request = new JobInfo(); request.setState(JobStateEnum.CLOSED); final Request post = getRequest(HttpMethod.POST, jobUrl(jobId)); try { marshalRequest(objectFactory.createJobInfo(request), post, APPLICATION_XML_UTF8); } catch (SalesforceException e) { callback.onResponse(null, e); return; } // make the call and parse the result doHttpRequest(post, new ClientResponseCallback() { @Override public void onResponse(InputStream response, SalesforceException ex) { JobInfo value = null; try { value = unmarshalResponse(response, post, JobInfo.class); } catch (SalesforceException e) { ex = e; } callback.onResponse(value, ex); } }); } @Override public void abortJob(String jobId, final JobInfoResponseCallback callback) { final JobInfo request = new JobInfo(); request.setState(JobStateEnum.ABORTED); final Request post = getRequest(HttpMethod.POST, jobUrl(jobId)); try { marshalRequest(objectFactory.createJobInfo(request), post, APPLICATION_XML_UTF8); } catch (SalesforceException e) { callback.onResponse(null, e); return; } // make the call and parse the result doHttpRequest(post, new ClientResponseCallback() { @Override public void onResponse(InputStream response, SalesforceException ex) { JobInfo value = null; try { value = unmarshalResponse(response, post, JobInfo.class); } catch (SalesforceException e) { ex = e; } callback.onResponse(value, ex); } }); } @Override public void createBatch(InputStream batchStream, String jobId, ContentType contentTypeEnum, final BatchInfoResponseCallback callback) { final Request post = getRequest(HttpMethod.POST, batchUrl(jobId, null)); post.content(new InputStreamContentProvider(batchStream)); post.header(HttpHeader.CONTENT_TYPE, getContentType(contentTypeEnum) + ";charset=" + StringUtil.__UTF8); // make the call and parse the result doHttpRequest(post, new ClientResponseCallback() { @Override public void onResponse(InputStream response, SalesforceException ex) { BatchInfo value = null; try { value = unmarshalResponse(response, post, BatchInfo.class); } catch (SalesforceException e) { ex = e; } callback.onResponse(value, ex); } }); } @Override public void getBatch(String jobId, String batchId, final BatchInfoResponseCallback callback) { final Request get = getRequest(HttpMethod.GET, batchUrl(jobId, batchId)); // make the call and parse the result doHttpRequest(get, new ClientResponseCallback() { @Override public void onResponse(InputStream response, SalesforceException ex) { BatchInfo value = null; try { value = unmarshalResponse(response, get, BatchInfo.class); } catch (SalesforceException e) { ex = e; } callback.onResponse(value, ex); } }); } @Override public void getAllBatches(String jobId, final BatchInfoListResponseCallback callback) { final Request get = getRequest(HttpMethod.GET, batchUrl(jobId, null)); // make the call and parse the result doHttpRequest(get, new ClientResponseCallback() { @Override public void onResponse(InputStream response, SalesforceException ex) { BatchInfoList value = null; try { value = unmarshalResponse(response, get, BatchInfoList.class); } catch (SalesforceException e) { ex = e; } callback.onResponse(value != null ? value.getBatchInfo() : null, ex); } }); } @Override public void getRequest(String jobId, String batchId, final StreamResponseCallback callback) { final Request get = getRequest(HttpMethod.GET, batchRequestUrl(jobId, batchId, null)); // make the call and parse the result doHttpRequest(get, new ClientResponseCallback() { @Override public void onResponse(InputStream response, SalesforceException ex) { callback.onResponse(response, ex); } }); } @Override public void getResults(String jobId, String batchId, final StreamResponseCallback callback) { final Request get = getRequest(HttpMethod.GET, batchResultUrl(jobId, batchId, null)); // make the call and return the result doHttpRequest(get, new ClientResponseCallback() { @Override public void onResponse(InputStream response, SalesforceException ex) { callback.onResponse(response, ex); } }); } @Override public void createBatchQuery(String jobId, String soqlQuery, ContentType jobContentType, final BatchInfoResponseCallback callback) { final Request post = getRequest(HttpMethod.POST, batchUrl(jobId, null)); final byte[] queryBytes; try { queryBytes = soqlQuery.getBytes(StringUtil.__UTF8); } catch (UnsupportedEncodingException e) { callback.onResponse(null, new SalesforceException("Unexpected exception: " + e.getMessage(), e)); return; } post.content(new BytesContentProvider(queryBytes)); post.header(HttpHeader.CONTENT_TYPE, getContentType(jobContentType) + ";charset=" + StringUtil.__UTF8); // make the call and parse the result doHttpRequest(post, new ClientResponseCallback() { @Override public void onResponse(InputStream response, SalesforceException ex) { BatchInfo value = null; try { value = unmarshalResponse(response, post, BatchInfo.class); } catch (SalesforceException e) { ex = e; } callback.onResponse(value, ex); } }); } @Override public void getQueryResultIds(String jobId, String batchId, final QueryResultIdsCallback callback) { final Request get = getRequest(HttpMethod.GET, batchResultUrl(jobId, batchId, null)); // make the call and parse the result doHttpRequest(get, new ClientResponseCallback() { @Override public void onResponse(InputStream response, SalesforceException ex) { QueryResultList value = null; try { value = unmarshalResponse(response, get, QueryResultList.class); } catch (SalesforceException e) { ex = e; } callback.onResponse(value != null ? Collections.unmodifiableList(value.getResult()) : null, ex); } }); } @Override public void getQueryResult(String jobId, String batchId, String resultId, final StreamResponseCallback callback) { final Request get = getRequest(HttpMethod.GET, batchResultUrl(jobId, batchId, resultId)); // make the call and parse the result doHttpRequest(get, new ClientResponseCallback() { @Override public void onResponse(InputStream response, SalesforceException ex) { callback.onResponse(response, ex); } }); } @Override protected void setAccessToken(Request request) { // replace old token request.getHeaders().put(TOKEN_HEADER, accessToken); } @Override protected void doHttpRequest(Request request, ClientResponseCallback callback) { // set access token for all requests setAccessToken(request); // set default charset request.header(HttpHeader.ACCEPT_CHARSET, StringUtil.__UTF8); // TODO check if this is really needed or not, since SF response content type seems fixed // check if the default accept content type must be used if (!request.getHeaders().contains(HttpHeader.ACCEPT)) { final String contentType = getContentType(DEFAULT_ACCEPT_TYPE); request.header(HttpHeader.ACCEPT, contentType); // request content type and charset is set by the request entity } super.doHttpRequest(request, callback); } private static String getContentType(ContentType type) { String result = null; switch (type) { case CSV: result = "text/csv"; break; case XML: result = "application/xml"; break; case ZIP_CSV: case ZIP_XML: result = type.toString().toLowerCase().replace('_', '/'); break; default: break; } return result; } @Override protected SalesforceException createRestException(Response response, InputStream responseContent) { // this must be of type Error try { final Error error = unmarshalResponse(responseContent, response.getRequest(), Error.class); final RestError restError = new RestError(); restError.setErrorCode(error.getExceptionCode()); restError.setMessage(error.getExceptionMessage()); return new SalesforceException(Arrays.asList(restError), response.getStatus()); } catch (SalesforceException e) { String msg = "Error un-marshaling Salesforce Error: " + e.getMessage(); return new SalesforceException(msg, e); } } private <T> T unmarshalResponse(InputStream response, Request request, Class<T> resultClass) throws SalesforceException { try { Unmarshaller unmarshaller = context.createUnmarshaller(); JAXBElement<T> result = unmarshaller.unmarshal(new StreamSource(response), resultClass); return result.getValue(); } catch (JAXBException e) { throw new SalesforceException( String.format("Error unmarshaling response {%s:%s} : %s", request.getMethod(), request.getURI(), e.getMessage()), e); } catch (IllegalArgumentException e) { throw new SalesforceException( String.format("Error unmarshaling response for {%s:%s} : %s", request.getMethod(), request.getURI(), e.getMessage()), e); } } private void marshalRequest(Object input, Request request, String contentType) throws SalesforceException { try { Marshaller marshaller = context.createMarshaller(); ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); marshaller.marshal(input, byteStream); request.content(new BytesContentProvider(contentType, byteStream.toByteArray())); } catch (JAXBException e) { throw new SalesforceException( String.format("Error marshaling request for {%s:%s} : %s", request.getMethod(), request.getURI(), e.getMessage()), e); } catch (IllegalArgumentException e) { throw new SalesforceException( String.format("Error marshaling request for {%s:%s} : %s", request.getMethod(), request.getURI(), e.getMessage()), e); } } private String jobUrl(String jobId) { if (jobId != null) { return super.instanceUrl + "/services/async/" + version + "/job/" + jobId; } else { return super.instanceUrl + "/services/async/" + version + "/job"; } } private String batchUrl(String jobId, String batchId) { if (batchId != null) { return jobUrl(jobId) + "/batch/" + batchId; } else { return jobUrl(jobId) + "/batch"; } } private String batchResultUrl(String jobId, String batchId, String resultId) { if (resultId != null) { return batchUrl(jobId, batchId) + "/result/" + resultId; } else { return batchUrl(jobId, batchId) + "/result"; } } private String batchRequestUrl(String jobId, String batchId, String requestId) { if (requestId != null) { return batchUrl(jobId, batchId) + "/request/" + requestId; } else { return batchUrl(jobId, batchId) + "/request"; } } }