├── BulkLoadControlledFeedMultipleBatch.java ├── BulkLoadControlledFeedSingleBatch.java ├── BulkLoadParallel.java ├── BulkLoadSerial.java └── ReadMe.md /BulkLoadControlledFeedMultipleBatch.java: -------------------------------------------------------------------------------- 1 | //args (object,file) 2 | //compile: javac -cp C:\temp\java\partner.jar;c:\temp\java\wsc\force-wsc-29.0.0.jar BulkLoadParallelWait10.java 3 | //run: java -cp .;partner.jar;wsc\force-wsc-29.0.0.jar BulkLoadParallelWait10 Order__c OrderDataOrdered.csv 4 | 5 | import java.io.*; 6 | import java.util.*; 7 | 8 | import com.sforce.async.*; 9 | import com.sforce.soap.partner.PartnerConnection; 10 | import com.sforce.ws.ConnectionException; 11 | import com.sforce.ws.ConnectorConfig; 12 | 13 | 14 | 15 | public class BulkLoadControlledFeedMultipleBatch { 16 | 17 | 18 | public static void main(String[] args) 19 | throws AsyncApiException, ConnectionException, IOException { 20 | BulkLoadControlledFeedMultipleBatch bl = new BulkLoadControlledFeedMultipleBatch(); 21 | // Replace arguments below with your credentials and test file name 22 | bl.runSample(args[0], "", "", args[1]); 23 | } 24 | 25 | /** 26 | * Creates a Bulk API job and uploads batches for a CSV file. 27 | */ 28 | public void runSample(String sobjectType, String userName, 29 | String password, String sampleFileName) 30 | throws AsyncApiException, ConnectionException, IOException { 31 | BulkConnection connection = getBulkConnection(userName, password); 32 | 33 | createBatchesFromCSVFile(sobjectType, connection, sampleFileName); 34 | } 35 | 36 | 37 | 38 | /** 39 | * Gets the results of the operation and checks for errors. 40 | */ 41 | private void checkResults(BulkConnection connection, JobInfo job, 42 | List batchInfoList) 43 | throws AsyncApiException, IOException { 44 | // batchInfoList was populated when batches were created and submitted 45 | for (BatchInfo b : batchInfoList) { 46 | CSVReader rdr = 47 | new CSVReader(connection.getBatchResultStream(job.getId(), b.getId())); 48 | List resultHeader = rdr.nextRecord(); 49 | int resultCols = resultHeader.size(); 50 | 51 | List row; 52 | while ((row = rdr.nextRecord()) != null) { 53 | Map resultInfo = new HashMap(); 54 | for (int i = 0; i < resultCols; i++) { 55 | resultInfo.put(resultHeader.get(i), row.get(i)); 56 | } 57 | boolean success = Boolean.valueOf(resultInfo.get("Success")); 58 | boolean created = Boolean.valueOf(resultInfo.get("Created")); 59 | String id = resultInfo.get("Id"); 60 | String error = resultInfo.get("Error"); 61 | if (success && created) { 62 | System.out.println("Created row with id " + id); 63 | } else if (!success) { 64 | System.out.println("Failed with error: " + error); 65 | } 66 | } 67 | } 68 | } 69 | 70 | 71 | 72 | private void closeJob(BulkConnection connection, String jobId) 73 | throws AsyncApiException { 74 | JobInfo job = new JobInfo(); 75 | job.setId(jobId); 76 | job.setState(JobStateEnum.Closed); 77 | connection.updateJob(job); 78 | } 79 | 80 | 81 | 82 | /** 83 | * Wait for a job to complete by polling the Bulk API. 84 | * 85 | * @param connection 86 | * BulkConnection used to check results. 87 | * @param job 88 | * The job awaiting completion. 89 | * @param batchInfoList 90 | * List of batches for this job. 91 | * @throws AsyncApiException 92 | */ 93 | private void awaitCompletion(BulkConnection connection, JobInfo job, 94 | List batchInfoList) 95 | throws AsyncApiException { 96 | long sleepTime = 0L; 97 | Set incomplete = new HashSet(); 98 | for (BatchInfo bi : batchInfoList) { 99 | incomplete.add(bi.getId()); 100 | } 101 | while (!incomplete.isEmpty()) { 102 | try { 103 | Thread.sleep(sleepTime); 104 | } catch (InterruptedException e) {} 105 | System.out.println("Awaiting results..." + incomplete.size()); 106 | sleepTime = 10000L; 107 | BatchInfo[] statusList = 108 | connection.getBatchInfoList(job.getId()).getBatchInfo(); 109 | for (BatchInfo b : statusList) { 110 | if (b.getState() == BatchStateEnum.Completed 111 | || b.getState() == BatchStateEnum.Failed) { 112 | if (incomplete.remove(b.getId())) { 113 | System.out.println("BATCH STATUS:\n" + b); 114 | } 115 | } 116 | } 117 | } 118 | } 119 | 120 | 121 | 122 | /** 123 | * Create a new job using the Bulk API. 124 | * 125 | * @param sobjectType 126 | * The object type being loaded, such as "Account" 127 | * @param connection 128 | * BulkConnection used to create the new job. 129 | * @return The JobInfo for the new job. 130 | * @throws AsyncApiException 131 | */ 132 | private JobInfo createJob(String sobjectType, BulkConnection connection) 133 | throws AsyncApiException { 134 | JobInfo job = new JobInfo(); 135 | job.setObject(sobjectType); 136 | job.setOperation(OperationEnum.insert); 137 | job.setContentType(ContentType.CSV); 138 | job = connection.createJob(job); 139 | System.out.println(job); 140 | return job; 141 | } 142 | 143 | 144 | 145 | /** 146 | * Create the BulkConnection used to call Bulk API operations. 147 | */ 148 | private BulkConnection getBulkConnection(String userName, String password) 149 | throws ConnectionException, AsyncApiException { 150 | ConnectorConfig partnerConfig = new ConnectorConfig(); 151 | partnerConfig.setUsername(userName); 152 | partnerConfig.setPassword(password); 153 | partnerConfig.setAuthEndpoint("https://login.salesforce.com/services/Soap/u/29.0"); 154 | // Creating the connection automatically handles login and stores 155 | // the session in partnerConfig 156 | new PartnerConnection(partnerConfig); 157 | // When PartnerConnection is instantiated, a login is implicitly 158 | // executed and, if successful, 159 | // a valid session is stored in the ConnectorConfig instance. 160 | // Use this key to initialize a BulkConnection: 161 | ConnectorConfig config = new ConnectorConfig(); 162 | config.setSessionId(partnerConfig.getSessionId()); 163 | // The endpoint for the Bulk API service is the same as for the normal 164 | // SOAP uri until the /Soap/ part. From here it's '/async/versionNumber' 165 | String soapEndpoint = partnerConfig.getServiceEndpoint(); 166 | String apiVersion = "29.0"; 167 | String restEndpoint = soapEndpoint.substring(0, soapEndpoint.indexOf("Soap/")) 168 | + "async/" + apiVersion; 169 | config.setRestEndpoint(restEndpoint); 170 | // This should only be false when doing debugging. 171 | config.setCompression(true); 172 | // Set this to true to see HTTP requests and responses on stdout 173 | config.setTraceMessage(false); 174 | BulkConnection connection = new BulkConnection(config); 175 | return connection; 176 | } 177 | 178 | /** 179 | * Create and upload batches using a CSV file. 180 | * The file into the appropriate size batch files. 181 | * 182 | * @param connection 183 | * Connection to use for creating batches 184 | * @param jobInfo 185 | * Job associated with new batches 186 | * @param csvFileName 187 | * The source file for batch data 188 | */ 189 | private List createBatchesFromCSVFile(String sobjectType, BulkConnection connection, 190 | String csvFileName) 191 | throws IOException, AsyncApiException { 192 | List batchInfos = new ArrayList(); 193 | BufferedReader rdr = new BufferedReader( 194 | new InputStreamReader(new FileInputStream(csvFileName)) 195 | ); 196 | // read the CSV header row 197 | byte[] headerBytes = (rdr.readLine() + "\n").getBytes("UTF-8"); 198 | int headerBytesLength = headerBytes.length; 199 | File tmpFile = File.createTempFile("bulkAPIInsert", ".csv"); 200 | JobInfo job; 201 | 202 | // Split the CSV file into multiple batches 203 | try { 204 | FileOutputStream tmpOut = new FileOutputStream(tmpFile); 205 | int maxBytesPerBatch = 10000000; // 10 million bytes per batch 206 | int maxRowsPerBatch = 10000; // 10 thousand rows per batch 207 | int maxBatchesPerJob = 10; 208 | int currentBytes = 0; 209 | int currentLines = 0; 210 | int currentBatch = 0; 211 | 212 | String nextLine; 213 | 214 | job = createJob(sobjectType, connection); 215 | 216 | while ((nextLine = rdr.readLine()) != null) { 217 | byte[] bytes = (nextLine + "\n").getBytes("UTF-8"); 218 | // Create a new batch when our batch size limit is reached 219 | if (currentBytes + bytes.length > maxBytesPerBatch 220 | || currentLines > maxRowsPerBatch) { 221 | 222 | createBatch(tmpOut, tmpFile, batchInfos, connection, job); 223 | currentBatch++; 224 | 225 | if (currentBatch == 10) { 226 | closeJob(connection, job.getId()); 227 | awaitCompletion(connection, job, batchInfos); 228 | checkResults(connection, job, batchInfos); 229 | tmpFile.delete(); 230 | batchInfos.clear(); 231 | currentBatch = 0; 232 | job = createJob(sobjectType, connection); 233 | } 234 | 235 | currentBytes = 0; 236 | currentLines = 0; 237 | } 238 | if (currentBytes == 0) { 239 | tmpOut = new FileOutputStream(tmpFile); 240 | tmpOut.write(headerBytes); 241 | currentBytes = headerBytesLength; 242 | currentLines = 1; 243 | } 244 | tmpOut.write(bytes); 245 | currentBytes += bytes.length; 246 | currentLines++; 247 | } 248 | // Finished processing all rows 249 | // Create a final batch for any remaining data 250 | if (currentLines > 1) { 251 | createBatch(tmpOut, tmpFile, batchInfos, connection, job); 252 | closeJob(connection, job.getId()); 253 | awaitCompletion(connection, job, batchInfos); 254 | checkResults(connection, job, batchInfos); 255 | tmpFile.delete(); 256 | batchInfos.clear(); 257 | } 258 | } finally { 259 | tmpFile.delete(); 260 | } 261 | return batchInfos; 262 | } 263 | 264 | 265 | 266 | /** 267 | * Create a batch by uploading the contents of the file. 268 | * This closes the output stream. 269 | * 270 | * @param tmpOut 271 | * The output stream used to write the CSV data for a single batch. 272 | * @param tmpFile 273 | * The file associated with the above stream. 274 | * @param batchInfos 275 | * The batch info for the newly created batch is added to this list. 276 | * @param connection 277 | * The BulkConnection used to create the new batch. 278 | * @param jobInfo 279 | * The JobInfo associated with the new batch. 280 | */ 281 | private void createBatch(FileOutputStream tmpOut, File tmpFile, 282 | List batchInfos, BulkConnection connection, JobInfo jobInfo) 283 | throws IOException, AsyncApiException { 284 | tmpOut.flush(); 285 | tmpOut.close(); 286 | FileInputStream tmpInputStream = new FileInputStream(tmpFile); 287 | try { 288 | BatchInfo batchInfo = 289 | connection.createBatchFromStream(jobInfo, tmpInputStream); 290 | System.out.println(batchInfo); 291 | batchInfos.add(batchInfo); 292 | 293 | } finally { 294 | tmpInputStream.close(); 295 | } 296 | } 297 | } -------------------------------------------------------------------------------- /BulkLoadControlledFeedSingleBatch.java: -------------------------------------------------------------------------------- 1 | //args (object,file) 2 | //compile: javac -cp C:\temp\java\partner.jar;c:\temp\java\wsc\force-wsc-29.0.0.jar BulkLoadParallelWait.java 3 | //run: java -cp .;partner.jar;wsc\force-wsc-29.0.0.jar BulkLoadParallelWait Order__c OrderDataParallelWait.csv 4 | 5 | import java.io.*; 6 | import java.util.*; 7 | 8 | import com.sforce.async.*; 9 | import com.sforce.soap.partner.PartnerConnection; 10 | import com.sforce.ws.ConnectionException; 11 | import com.sforce.ws.ConnectorConfig; 12 | 13 | 14 | 15 | public class BulkLoadControlledFeedSingleBatch { 16 | 17 | 18 | public static void main(String[] args) 19 | throws AsyncApiException, ConnectionException, IOException { 20 | BulkLoadControlledFeedSingleBatch bl = new BulkLoadControlledFeedSingleBatch(); 21 | // Replace arguments below with your credentials and test file name 22 | bl.runSample(args[0], "", "", args[1]); 23 | } 24 | 25 | /** 26 | * Creates a Bulk API job and uploads batches for a CSV file. 27 | */ 28 | public void runSample(String sobjectType, String userName, 29 | String password, String sampleFileName) 30 | throws AsyncApiException, ConnectionException, IOException { 31 | BulkConnection connection = getBulkConnection(userName, password); 32 | 33 | createBatchesFromCSVFile(sobjectType, connection, sampleFileName); 34 | } 35 | 36 | 37 | 38 | /** 39 | * Gets the results of the operation and checks for errors. 40 | */ 41 | private void checkResults(BulkConnection connection, JobInfo job, 42 | List batchInfoList) 43 | throws AsyncApiException, IOException { 44 | // batchInfoList was populated when batches were created and submitted 45 | for (BatchInfo b : batchInfoList) { 46 | CSVReader rdr = 47 | new CSVReader(connection.getBatchResultStream(job.getId(), b.getId())); 48 | List resultHeader = rdr.nextRecord(); 49 | int resultCols = resultHeader.size(); 50 | 51 | List row; 52 | while ((row = rdr.nextRecord()) != null) { 53 | Map resultInfo = new HashMap(); 54 | for (int i = 0; i < resultCols; i++) { 55 | resultInfo.put(resultHeader.get(i), row.get(i)); 56 | } 57 | boolean success = Boolean.valueOf(resultInfo.get("Success")); 58 | boolean created = Boolean.valueOf(resultInfo.get("Created")); 59 | String id = resultInfo.get("Id"); 60 | String error = resultInfo.get("Error"); 61 | if (success && created) { 62 | System.out.println("Created row with id " + id); 63 | } else if (!success) { 64 | System.out.println("Failed with error: " + error); 65 | } 66 | } 67 | } 68 | } 69 | 70 | 71 | 72 | private void closeJob(BulkConnection connection, String jobId) 73 | throws AsyncApiException { 74 | JobInfo job = new JobInfo(); 75 | job.setId(jobId); 76 | job.setState(JobStateEnum.Closed); 77 | connection.updateJob(job); 78 | } 79 | 80 | 81 | 82 | /** 83 | * Wait for a job to complete by polling the Bulk API. 84 | * 85 | * @param connection 86 | * BulkConnection used to check results. 87 | * @param job 88 | * The job awaiting completion. 89 | * @param batchInfoList 90 | * List of batches for this job. 91 | * @throws AsyncApiException 92 | */ 93 | private void awaitCompletion(BulkConnection connection, JobInfo job, 94 | List batchInfoList) 95 | throws AsyncApiException { 96 | long sleepTime = 0L; 97 | Set incomplete = new HashSet(); 98 | for (BatchInfo bi : batchInfoList) { 99 | incomplete.add(bi.getId()); 100 | } 101 | while (!incomplete.isEmpty()) { 102 | try { 103 | Thread.sleep(sleepTime); 104 | } catch (InterruptedException e) {} 105 | System.out.println("Awaiting results..." + incomplete.size()); 106 | sleepTime = 10000L; 107 | BatchInfo[] statusList = 108 | connection.getBatchInfoList(job.getId()).getBatchInfo(); 109 | for (BatchInfo b : statusList) { 110 | if (b.getState() == BatchStateEnum.Completed 111 | || b.getState() == BatchStateEnum.Failed) { 112 | if (incomplete.remove(b.getId())) { 113 | System.out.println("BATCH STATUS:\n" + b); 114 | } 115 | } 116 | } 117 | } 118 | } 119 | 120 | 121 | 122 | /** 123 | * Create a new job using the Bulk API. 124 | * 125 | * @param sobjectType 126 | * The object type being loaded, such as "Account" 127 | * @param connection 128 | * BulkConnection used to create the new job. 129 | * @return The JobInfo for the new job. 130 | * @throws AsyncApiException 131 | */ 132 | private JobInfo createJob(String sobjectType, BulkConnection connection) 133 | throws AsyncApiException { 134 | JobInfo job = new JobInfo(); 135 | job.setObject(sobjectType); 136 | job.setOperation(OperationEnum.insert); 137 | job.setContentType(ContentType.CSV); 138 | job = connection.createJob(job); 139 | System.out.println(job); 140 | return job; 141 | } 142 | 143 | 144 | 145 | /** 146 | * Create the BulkConnection used to call Bulk API operations. 147 | */ 148 | private BulkConnection getBulkConnection(String userName, String password) 149 | throws ConnectionException, AsyncApiException { 150 | ConnectorConfig partnerConfig = new ConnectorConfig(); 151 | partnerConfig.setUsername(userName); 152 | partnerConfig.setPassword(password); 153 | partnerConfig.setAuthEndpoint("https://login.salesforce.com/services/Soap/u/29.0"); 154 | // Creating the connection automatically handles login and stores 155 | // the session in partnerConfig 156 | new PartnerConnection(partnerConfig); 157 | // When PartnerConnection is instantiated, a login is implicitly 158 | // executed and, if successful, 159 | // a valid session is stored in the ConnectorConfig instance. 160 | // Use this key to initialize a BulkConnection: 161 | ConnectorConfig config = new ConnectorConfig(); 162 | config.setSessionId(partnerConfig.getSessionId()); 163 | // The endpoint for the Bulk API service is the same as for the normal 164 | // SOAP uri until the /Soap/ part. From here it's '/async/versionNumber' 165 | String soapEndpoint = partnerConfig.getServiceEndpoint(); 166 | String apiVersion = "29.0"; 167 | String restEndpoint = soapEndpoint.substring(0, soapEndpoint.indexOf("Soap/")) 168 | + "async/" + apiVersion; 169 | config.setRestEndpoint(restEndpoint); 170 | // This should only be false when doing debugging. 171 | config.setCompression(true); 172 | // Set this to true to see HTTP requests and responses on stdout 173 | config.setTraceMessage(false); 174 | BulkConnection connection = new BulkConnection(config); 175 | return connection; 176 | } 177 | 178 | /** 179 | * Create and upload batches using a CSV file. 180 | * The file into the appropriate size batch files. 181 | * 182 | * @param connection 183 | * Connection to use for creating batches 184 | * @param jobInfo 185 | * Job associated with new batches 186 | * @param csvFileName 187 | * The source file for batch data 188 | */ 189 | private List createBatchesFromCSVFile(String sobjectType, BulkConnection connection, 190 | String csvFileName) 191 | throws IOException, AsyncApiException { 192 | List batchInfos = new ArrayList(); 193 | BufferedReader rdr = new BufferedReader( 194 | new InputStreamReader(new FileInputStream(csvFileName)) 195 | ); 196 | // read the CSV header row 197 | byte[] headerBytes = (rdr.readLine() + "\n").getBytes("UTF-8"); 198 | int headerBytesLength = headerBytes.length; 199 | File tmpFile = File.createTempFile("bulkAPIInsert", ".csv"); 200 | JobInfo job; 201 | 202 | // Split the CSV file into multiple batches 203 | try { 204 | FileOutputStream tmpOut = new FileOutputStream(tmpFile); 205 | int maxBytesPerBatch = 10000000; // 10 million bytes per batch 206 | int maxRowsPerBatch = 10000; // 10 thousand rows per batch 207 | int currentBytes = 0; 208 | int currentLines = 0; 209 | String nextLine; 210 | while ((nextLine = rdr.readLine()) != null) { 211 | byte[] bytes = (nextLine + "\n").getBytes("UTF-8"); 212 | // Create a new batch when our batch size limit is reached 213 | if (currentBytes + bytes.length > maxBytesPerBatch 214 | || currentLines > maxRowsPerBatch) { 215 | 216 | job = createJob(sobjectType, connection); 217 | createBatch(tmpOut, tmpFile, batchInfos, connection, job); 218 | closeJob(connection, job.getId()); 219 | awaitCompletion(connection, job, batchInfos); 220 | checkResults(connection, job, batchInfos); 221 | tmpFile.delete(); 222 | batchInfos.clear(); 223 | 224 | currentBytes = 0; 225 | currentLines = 0; 226 | } 227 | if (currentBytes == 0) { 228 | tmpOut = new FileOutputStream(tmpFile); 229 | tmpOut.write(headerBytes); 230 | currentBytes = headerBytesLength; 231 | currentLines = 1; 232 | } 233 | tmpOut.write(bytes); 234 | currentBytes += bytes.length; 235 | currentLines++; 236 | } 237 | // Finished processing all rows 238 | // Create a final batch for any remaining data 239 | if (currentLines > 1) { 240 | job = createJob(sobjectType, connection); 241 | createBatch(tmpOut, tmpFile, batchInfos, connection, job); 242 | closeJob(connection, job.getId()); 243 | awaitCompletion(connection, job, batchInfos); 244 | checkResults(connection, job, batchInfos); 245 | tmpFile.delete(); 246 | batchInfos.clear(); 247 | } 248 | } finally { 249 | tmpFile.delete(); 250 | } 251 | return batchInfos; 252 | } 253 | 254 | 255 | 256 | /** 257 | * Create a batch by uploading the contents of the file. 258 | * This closes the output stream. 259 | * 260 | * @param tmpOut 261 | * The output stream used to write the CSV data for a single batch. 262 | * @param tmpFile 263 | * The file associated with the above stream. 264 | * @param batchInfos 265 | * The batch info for the newly created batch is added to this list. 266 | * @param connection 267 | * The BulkConnection used to create the new batch. 268 | * @param jobInfo 269 | * The JobInfo associated with the new batch. 270 | */ 271 | private void createBatch(FileOutputStream tmpOut, File tmpFile, 272 | List batchInfos, BulkConnection connection, JobInfo jobInfo) 273 | throws IOException, AsyncApiException { 274 | tmpOut.flush(); 275 | tmpOut.close(); 276 | FileInputStream tmpInputStream = new FileInputStream(tmpFile); 277 | try { 278 | BatchInfo batchInfo = 279 | connection.createBatchFromStream(jobInfo, tmpInputStream); 280 | System.out.println(batchInfo); 281 | batchInfos.add(batchInfo); 282 | 283 | } finally { 284 | tmpInputStream.close(); 285 | } 286 | } 287 | } -------------------------------------------------------------------------------- /BulkLoadParallel.java: -------------------------------------------------------------------------------- 1 | //args (object,file) 2 | //compile: javac -cp C:\temp\java\partner.jar;c:\temp\java\wsc\force-wsc-29.0.0.jar BulkLoadParallel.java 3 | //run: java -cp .;partner.jar;wsc\force-wsc-29.0.0.jar BulkLoadParallel Order__c OrderData.csv 4 | 5 | import java.io.*; 6 | import java.util.*; 7 | 8 | import com.sforce.async.*; 9 | import com.sforce.soap.partner.PartnerConnection; 10 | import com.sforce.ws.ConnectionException; 11 | import com.sforce.ws.ConnectorConfig; 12 | 13 | 14 | 15 | public class BulkLoadParallel { 16 | 17 | public static void main(String[] args) 18 | throws AsyncApiException, ConnectionException, IOException { 19 | BulkLoadParallel bl = new BulkLoadParallel(); 20 | // Replace arguments below with your credentials and test file name 21 | bl.runSample(args[0], "", "", args[1]); 22 | } 23 | 24 | /** 25 | * Creates a Bulk API job and uploads batches for a CSV file. 26 | */ 27 | public void runSample(String sobjectType, String userName, 28 | String password, String sampleFileName) 29 | throws AsyncApiException, ConnectionException, IOException { 30 | BulkConnection connection = getBulkConnection(userName, password); 31 | JobInfo job = createJob(sobjectType, connection); 32 | List batchInfoList = createBatchesFromCSVFile(connection, job, 33 | sampleFileName); 34 | closeJob(connection, job.getId()); 35 | awaitCompletion(connection, job, batchInfoList); 36 | checkResults(connection, job, batchInfoList); 37 | } 38 | 39 | 40 | 41 | /** 42 | * Gets the results of the operation and checks for errors. 43 | */ 44 | private void checkResults(BulkConnection connection, JobInfo job, 45 | List batchInfoList) 46 | throws AsyncApiException, IOException { 47 | // batchInfoList was populated when batches were created and submitted 48 | for (BatchInfo b : batchInfoList) { 49 | CSVReader rdr = 50 | new CSVReader(connection.getBatchResultStream(job.getId(), b.getId())); 51 | List resultHeader = rdr.nextRecord(); 52 | int resultCols = resultHeader.size(); 53 | 54 | List row; 55 | while ((row = rdr.nextRecord()) != null) { 56 | Map resultInfo = new HashMap(); 57 | for (int i = 0; i < resultCols; i++) { 58 | resultInfo.put(resultHeader.get(i), row.get(i)); 59 | } 60 | boolean success = Boolean.valueOf(resultInfo.get("Success")); 61 | boolean created = Boolean.valueOf(resultInfo.get("Created")); 62 | String id = resultInfo.get("Id"); 63 | String error = resultInfo.get("Error"); 64 | if (success && created) { 65 | System.out.println("Created row with id " + id); 66 | } else if (!success) { 67 | System.out.println("Failed with error: " + error); 68 | } 69 | } 70 | } 71 | } 72 | 73 | private void closeJob(BulkConnection connection, String jobId) 74 | throws AsyncApiException { 75 | JobInfo job = new JobInfo(); 76 | job.setId(jobId); 77 | job.setState(JobStateEnum.Closed); 78 | connection.updateJob(job); 79 | } 80 | 81 | 82 | 83 | /** 84 | * Wait for a job to complete by polling the Bulk API. 85 | * 86 | * @param connection 87 | * BulkConnection used to check results. 88 | * @param job 89 | * The job awaiting completion. 90 | * @param batchInfoList 91 | * List of batches for this job. 92 | * @throws AsyncApiException 93 | */ 94 | private void awaitCompletion(BulkConnection connection, JobInfo job, 95 | List batchInfoList) 96 | throws AsyncApiException { 97 | long sleepTime = 0L; 98 | Set incomplete = new HashSet(); 99 | for (BatchInfo bi : batchInfoList) { 100 | incomplete.add(bi.getId()); 101 | } 102 | while (!incomplete.isEmpty()) { 103 | try { 104 | Thread.sleep(sleepTime); 105 | } catch (InterruptedException e) {} 106 | System.out.println("Awaiting results..." + incomplete.size()); 107 | sleepTime = 10000L; 108 | BatchInfo[] statusList = 109 | connection.getBatchInfoList(job.getId()).getBatchInfo(); 110 | for (BatchInfo b : statusList) { 111 | if (b.getState() == BatchStateEnum.Completed 112 | || b.getState() == BatchStateEnum.Failed) { 113 | if (incomplete.remove(b.getId())) { 114 | System.out.println("BATCH STATUS:\n" + b); 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | 122 | 123 | /** 124 | * Create a new job using the Bulk API. 125 | * 126 | * @param sobjectType 127 | * The object type being loaded, such as "Account" 128 | * @param connection 129 | * BulkConnection used to create the new job. 130 | * @return The JobInfo for the new job. 131 | * @throws AsyncApiException 132 | */ 133 | private JobInfo createJob(String sobjectType, BulkConnection connection) 134 | throws AsyncApiException { 135 | JobInfo job = new JobInfo(); 136 | job.setObject(sobjectType); 137 | job.setOperation(OperationEnum.insert); 138 | job.setContentType(ContentType.CSV); 139 | job = connection.createJob(job); 140 | System.out.println(job); 141 | return job; 142 | } 143 | 144 | 145 | 146 | /** 147 | * Create the BulkConnection used to call Bulk API operations. 148 | */ 149 | private BulkConnection getBulkConnection(String userName, String password) 150 | throws ConnectionException, AsyncApiException { 151 | ConnectorConfig partnerConfig = new ConnectorConfig(); 152 | partnerConfig.setUsername(userName); 153 | partnerConfig.setPassword(password); 154 | partnerConfig.setAuthEndpoint("https://login.salesforce.com/services/Soap/u/29.0"); 155 | // Creating the connection automatically handles login and stores 156 | // the session in partnerConfig 157 | new PartnerConnection(partnerConfig); 158 | // When PartnerConnection is instantiated, a login is implicitly 159 | // executed and, if successful, 160 | // a valid session is stored in the ConnectorConfig instance. 161 | // Use this key to initialize a BulkConnection: 162 | ConnectorConfig config = new ConnectorConfig(); 163 | config.setSessionId(partnerConfig.getSessionId()); 164 | // The endpoint for the Bulk API service is the same as for the normal 165 | // SOAP uri until the /Soap/ part. From here it's '/async/versionNumber' 166 | String soapEndpoint = partnerConfig.getServiceEndpoint(); 167 | String apiVersion = "29.0"; 168 | String restEndpoint = soapEndpoint.substring(0, soapEndpoint.indexOf("Soap/")) 169 | + "async/" + apiVersion; 170 | config.setRestEndpoint(restEndpoint); 171 | // This should only be false when doing debugging. 172 | config.setCompression(true); 173 | // Set this to true to see HTTP requests and responses on stdout 174 | config.setTraceMessage(false); 175 | BulkConnection connection = new BulkConnection(config); 176 | return connection; 177 | } 178 | 179 | 180 | 181 | /** 182 | * Create and upload batches using a CSV file. 183 | * The file into the appropriate size batch files. 184 | * 185 | * @param connection 186 | * Connection to use for creating batches 187 | * @param jobInfo 188 | * Job associated with new batches 189 | * @param csvFileName 190 | * The source file for batch data 191 | */ 192 | private List createBatchesFromCSVFile(BulkConnection connection, 193 | JobInfo jobInfo, String csvFileName) 194 | throws IOException, AsyncApiException { 195 | List batchInfos = new ArrayList(); 196 | BufferedReader rdr = new BufferedReader( 197 | new InputStreamReader(new FileInputStream(csvFileName)) 198 | ); 199 | // read the CSV header row 200 | byte[] headerBytes = (rdr.readLine() + "\n").getBytes("UTF-8"); 201 | int headerBytesLength = headerBytes.length; 202 | File tmpFile = File.createTempFile("bulkAPIInsert", ".csv"); 203 | 204 | // Split the CSV file into multiple batches 205 | try { 206 | FileOutputStream tmpOut = new FileOutputStream(tmpFile); 207 | int maxBytesPerBatch = 10000000; // 10 million bytes per batch 208 | int maxRowsPerBatch = 10000; // 10 thousand rows per batch 209 | int currentBytes = 0; 210 | int currentLines = 0; 211 | String nextLine; 212 | while ((nextLine = rdr.readLine()) != null) { 213 | byte[] bytes = (nextLine + "\n").getBytes("UTF-8"); 214 | // Create a new batch when our batch size limit is reached 215 | if (currentBytes + bytes.length > maxBytesPerBatch 216 | || currentLines > maxRowsPerBatch) { 217 | createBatch(tmpOut, tmpFile, batchInfos, connection, jobInfo); 218 | currentBytes = 0; 219 | currentLines = 0; 220 | } 221 | if (currentBytes == 0) { 222 | tmpOut = new FileOutputStream(tmpFile); 223 | tmpOut.write(headerBytes); 224 | currentBytes = headerBytesLength; 225 | currentLines = 1; 226 | } 227 | tmpOut.write(bytes); 228 | currentBytes += bytes.length; 229 | currentLines++; 230 | } 231 | // Finished processing all rows 232 | // Create a final batch for any remaining data 233 | if (currentLines > 1) { 234 | createBatch(tmpOut, tmpFile, batchInfos, connection, jobInfo); 235 | } 236 | } finally { 237 | tmpFile.delete(); 238 | } 239 | return batchInfos; 240 | } 241 | 242 | /** 243 | * Create a batch by uploading the contents of the file. 244 | * This closes the output stream. 245 | * 246 | * @param tmpOut 247 | * The output stream used to write the CSV data for a single batch. 248 | * @param tmpFile 249 | * The file associated with the above stream. 250 | * @param batchInfos 251 | * The batch info for the newly created batch is added to this list. 252 | * @param connection 253 | * The BulkConnection used to create the new batch. 254 | * @param jobInfo 255 | * The JobInfo associated with the new batch. 256 | */ 257 | private void createBatch(FileOutputStream tmpOut, File tmpFile, 258 | List batchInfos, BulkConnection connection, JobInfo jobInfo) 259 | throws IOException, AsyncApiException { 260 | tmpOut.flush(); 261 | tmpOut.close(); 262 | FileInputStream tmpInputStream = new FileInputStream(tmpFile); 263 | try { 264 | BatchInfo batchInfo = 265 | connection.createBatchFromStream(jobInfo, tmpInputStream); 266 | System.out.println(batchInfo); 267 | batchInfos.add(batchInfo); 268 | 269 | } finally { 270 | tmpInputStream.close(); 271 | } 272 | } 273 | } -------------------------------------------------------------------------------- /BulkLoadSerial.java: -------------------------------------------------------------------------------- 1 | //args (object,file) 2 | //compile: javac -cp C:\temp\java\partner.jar;c:\temp\java\wsc\force-wsc-29.0.0.jar BulkLoadSerial.java 3 | //run: java -cp .;partner.jar;wsc\force-wsc-29.0.0.jar BulkLoadSerial Order__c OrderDataSerial.csv 4 | 5 | import java.io.*; 6 | import java.util.*; 7 | 8 | import com.sforce.async.*; 9 | import com.sforce.soap.partner.PartnerConnection; 10 | import com.sforce.ws.ConnectionException; 11 | import com.sforce.ws.ConnectorConfig; 12 | 13 | 14 | 15 | public class BulkLoadSerial { 16 | 17 | 18 | public static void main(String[] args) throws AsyncApiException, ConnectionException, IOException { 19 | BulkLoadSerial bl = new BulkLoadSerial(); 20 | bl.runSample(args[0], "", "", args[1]); 21 | } 22 | 23 | /** 24 | * Creates a Bulk API job and uploads batches for a CSV file. 25 | */ 26 | public void runSample(String sobjectType, String userName, String password, String sampleFileName) 27 | throws AsyncApiException, ConnectionException, IOException { 28 | BulkConnection connection = getBulkConnection(userName, password); 29 | JobInfo job = createJob(sobjectType, connection); 30 | List batchInfoList = createBatchesFromCSVFile(connection, job, sampleFileName); 31 | closeJob(connection, job.getId()); 32 | awaitCompletion(connection, job, batchInfoList); 33 | checkResults(connection, job, batchInfoList); 34 | } 35 | 36 | 37 | 38 | /** 39 | * Gets the results of the operation and checks for errors. 40 | */ 41 | private void checkResults(BulkConnection connection, JobInfo job, List batchInfoList) 42 | throws AsyncApiException, IOException { 43 | // batchInfoList was populated when batches were created and submitted 44 | for (BatchInfo b : batchInfoList) { 45 | CSVReader rdr = new CSVReader(connection.getBatchResultStream(job.getId(), b.getId())); 46 | List resultHeader = rdr.nextRecord(); 47 | int resultCols = resultHeader.size(); 48 | 49 | List row; 50 | while ((row = rdr.nextRecord()) != null) { 51 | Map resultInfo = new HashMap(); 52 | for (int i = 0; i < resultCols; i++) { 53 | resultInfo.put(resultHeader.get(i), row.get(i)); 54 | } 55 | boolean success = Boolean.valueOf(resultInfo.get("Success")); 56 | boolean created = Boolean.valueOf(resultInfo.get("Created")); 57 | String id = resultInfo.get("Id"); 58 | String error = resultInfo.get("Error"); 59 | if (success && created) { 60 | System.out.println("Created row with id " + id); 61 | } else if (!success) { 62 | System.out.println("Failed with error: " + error); 63 | } 64 | } 65 | } 66 | } 67 | 68 | private void closeJob(BulkConnection connection, String jobId) 69 | throws AsyncApiException { 70 | JobInfo job = new JobInfo(); 71 | job.setId(jobId); 72 | job.setState(JobStateEnum.Closed); 73 | connection.updateJob(job); 74 | } 75 | 76 | 77 | 78 | /** 79 | * Wait for a job to complete by polling the Bulk API. 80 | * 81 | * @param connection 82 | * BulkConnection used to check results. 83 | * @param job 84 | * The job awaiting completion. 85 | * @param batchInfoList 86 | * List of batches for this job. 87 | * @throws AsyncApiException 88 | */ 89 | private void awaitCompletion(BulkConnection connection, JobInfo job, List batchInfoList) 90 | throws AsyncApiException { 91 | long sleepTime = 0L; 92 | Set incomplete = new HashSet(); 93 | for (BatchInfo bi : batchInfoList) { 94 | incomplete.add(bi.getId()); 95 | } 96 | while (!incomplete.isEmpty()) { 97 | try { 98 | Thread.sleep(sleepTime); 99 | } catch (InterruptedException e) {} 100 | System.out.println("Awaiting results..." + incomplete.size()); 101 | sleepTime = 10000L; 102 | BatchInfo[] statusList = connection.getBatchInfoList(job.getId()).getBatchInfo(); 103 | for (BatchInfo b : statusList) { 104 | if (b.getState() == BatchStateEnum.Completed || b.getState() == BatchStateEnum.Failed) { 105 | if (incomplete.remove(b.getId())) { 106 | System.out.println("BATCH STATUS:\n" + b); 107 | } 108 | } 109 | } 110 | } 111 | } 112 | 113 | 114 | 115 | /** 116 | * Create a new job using the Bulk API. 117 | * 118 | * @param sobjectType 119 | * The object type being loaded, such as "Account" 120 | * @param connection 121 | * BulkConnection used to create the new job. 122 | * @return The JobInfo for the new job. 123 | * @throws AsyncApiException 124 | */ 125 | private JobInfo createJob(String sobjectType, BulkConnection connection) throws AsyncApiException { 126 | JobInfo job = new JobInfo(); 127 | job.setObject(sobjectType); 128 | job.setOperation(OperationEnum.insert); 129 | job.setContentType(ContentType.CSV); 130 | job.setConcurrencyMode(ConcurrencyMode.Serial); 131 | job = connection.createJob(job); 132 | System.out.println(job); 133 | return job; 134 | } 135 | 136 | 137 | 138 | /** 139 | * Create the BulkConnection used to call Bulk API operations. 140 | */ 141 | private BulkConnection getBulkConnection(String userName, String password) throws ConnectionException, AsyncApiException { 142 | ConnectorConfig partnerConfig = new ConnectorConfig(); 143 | partnerConfig.setUsername(userName); 144 | partnerConfig.setPassword(password); 145 | partnerConfig.setAuthEndpoint("https://login.salesforce.com/services/Soap/u/29.0"); 146 | // Creating the connection automatically handles login and stores 147 | // the session in partnerConfig 148 | new PartnerConnection(partnerConfig); 149 | // When PartnerConnection is instantiated, a login is implicitly 150 | // executed and, if successful, 151 | // a valid session is stored in the ConnectorConfig instance. 152 | // Use this key to initialize a BulkConnection: 153 | ConnectorConfig config = new ConnectorConfig(); 154 | config.setSessionId(partnerConfig.getSessionId()); 155 | // The endpoint for the Bulk API service is the same as for the normal 156 | // SOAP uri until the /Soap/ part. From here it's '/async/versionNumber' 157 | String soapEndpoint = partnerConfig.getServiceEndpoint(); 158 | String apiVersion = "29.0"; 159 | String restEndpoint = soapEndpoint.substring(0, soapEndpoint.indexOf("Soap/")) + "async/" + apiVersion; 160 | config.setRestEndpoint(restEndpoint); 161 | // This should only be false when doing debugging. 162 | config.setCompression(true); 163 | // Set this to true to see HTTP requests and responses on stdout 164 | config.setTraceMessage(false); 165 | BulkConnection connection = new BulkConnection(config); 166 | return connection; 167 | } 168 | 169 | 170 | 171 | /** 172 | * Create and upload batches using a CSV file. 173 | * The file into the appropriate size batch files. 174 | * 175 | * @param connection 176 | * Connection to use for creating batches 177 | * @param jobInfo 178 | * Job associated with new batches 179 | * @param csvFileName 180 | * The source file for batch data 181 | */ 182 | private List createBatchesFromCSVFile(BulkConnection connection, JobInfo jobInfo, String csvFileName) 183 | throws IOException, AsyncApiException { 184 | List batchInfos = new ArrayList(); 185 | BufferedReader rdr = new BufferedReader( 186 | new InputStreamReader(new FileInputStream(csvFileName)) 187 | ); 188 | // read the CSV header row 189 | byte[] headerBytes = (rdr.readLine() + "\n").getBytes("UTF-8"); 190 | int headerBytesLength = headerBytes.length; 191 | File tmpFile = File.createTempFile("bulkAPIInsert", ".csv"); 192 | 193 | // Split the CSV file into multiple batches 194 | try { 195 | FileOutputStream tmpOut = new FileOutputStream(tmpFile); 196 | int maxBytesPerBatch = 10000000; // 10 million bytes per batch 197 | int maxRowsPerBatch = 10000; // 10 thousand rows per batch 198 | int currentBytes = 0; 199 | int currentLines = 0; 200 | String nextLine; 201 | while ((nextLine = rdr.readLine()) != null) { 202 | byte[] bytes = (nextLine + "\n").getBytes("UTF-8"); 203 | // Create a new batch when our batch size limit is reached 204 | if (currentBytes + bytes.length > maxBytesPerBatch || currentLines > maxRowsPerBatch) { 205 | createBatch(tmpOut, tmpFile, batchInfos, connection, jobInfo); 206 | currentBytes = 0; 207 | currentLines = 0; 208 | } 209 | if (currentBytes == 0) { 210 | tmpOut = new FileOutputStream(tmpFile); 211 | tmpOut.write(headerBytes); 212 | currentBytes = headerBytesLength; 213 | currentLines = 1; 214 | } 215 | tmpOut.write(bytes); 216 | currentBytes += bytes.length; 217 | currentLines++; 218 | } 219 | // Finished processing all rows 220 | // Create a final batch for any remaining data 221 | if (currentLines > 1) { 222 | createBatch(tmpOut, tmpFile, batchInfos, connection, jobInfo); 223 | } 224 | } finally { 225 | tmpFile.delete(); 226 | } 227 | return batchInfos; 228 | } 229 | 230 | /** 231 | * Create a batch by uploading the contents of the file. 232 | * This closes the output stream. 233 | * 234 | * @param tmpOut 235 | * The output stream used to write the CSV data for a single batch. 236 | * @param tmpFile 237 | * The file associated with the above stream. 238 | * @param batchInfos 239 | * The batch info for the newly created batch is added to this list. 240 | * @param connection 241 | * The BulkConnection used to create the new batch. 242 | * @param jobInfo 243 | * The JobInfo associated with the new batch. 244 | */ 245 | private void createBatch(FileOutputStream tmpOut, File tmpFile, List batchInfos, BulkConnection connection, JobInfo jobInfo) 246 | throws IOException, AsyncApiException { 247 | tmpOut.flush(); 248 | tmpOut.close(); 249 | FileInputStream tmpInputStream = new FileInputStream(tmpFile); 250 | try { 251 | BatchInfo batchInfo = connection.createBatchFromStream(jobInfo, tmpInputStream); 252 | System.out.println(batchInfo); 253 | batchInfos.add(batchInfo); 254 | 255 | } finally { 256 | tmpInputStream.close(); 257 | } 258 | } 259 | } -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | salesforce-bulkAPI-parallelism 2 | ================================ 3 | 4 | Source code for 2/2014 webinar Salesforce API Series: Fast Parallel Data Loading with the Bulk API --------------------------------------------------------------------------------