Creating an Attachment SObject complete with its body using the REST API from Java

Exporting the body of an Attachment using the REST API is fairly straightforward: the “Body” field of the Attachment JSON contains the URL to GET the raw bytes for the Attachment body from. But it took me a while to get the importing of an Attachment and its body to work because PATCH or POST to the equivalent body URL is not allowed.

This documentation Insert or Update Blob Data explains generically what needs to be done. But it took me a bit of time to work out how to do this in Java using the Apache HttpClient. Here is a stripped down version of what I ended up with that might help you if you are writing similar Java code:

package com.claimvantage.ant;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.PartBase;

public class AttachmentExample {
    
    private class JsonPart extends PartBase {
        
        private byte[] bytes;
        
        public JsonPart(String name, String json) throws IOException {
            super(name, "application/json", "UTF-8", null);
            this.bytes = json.getBytes("UTF-8");
        }
        
        @Override
        protected void sendData(OutputStream os) throws IOException {
            os.write(bytes);
        }

        @Override
        protected long lengthOfData() throws IOException {
            return bytes.length;
        }
    }
    
    private String baseUrl;     // Initialization not shown here
    private String sessionId;   // Initialization not shown here
    
    /**
     * Create attachment SObject from its JSON populating its Body from a file at the same time.
     */
    public void createAttachment(String attachmentJson, File attachmentFile) throws Exception {
        
        PostMethod post = new PostMethod(baseUrl + "/services/data/v23.0/sobjects/Attachment");
        post.setRequestHeader("Authorization", "OAuth " + sessionId);
        Part[] parts = new Part[] {
                new JsonPart("Json", attachmentJson),
                new FilePart("Body", attachmentFile)
                };
        post.setRequestEntity(new MultipartRequestEntity(parts, post.getParams()));
        try {
            new HttpClient().executeMethod(post);
            if (post.getStatusCode() == HttpStatus.SC_CREATED) {
                // Logic for OK
            } else {
                // Error handling logic
            }
        } finally {
            post.releaseConnection();
        }
    }
}

“New” action pre-processing override results in “Error: Invalid Data…” on “Save & New”

You can create a custom page and controller and then hook them up to the “New” action for an object – see e.g. Overriding a Standard Button. One situation where this is useful is where you want to do some of your own processing and then forward on to the default UI page.

Here is the sort of page you might use for this purpose:

<apex:page
    standardController="MyCustomObject__c"
    extensions="MyCustomObjectController"
    action="{!url}
    />

and controller:

public with sharing class MyCustomObjectController {

  // Controller extension
  public MyCustomObjectController(ApexPages.StandardController ignored) {
  }

  public PageReference url() {

    // Custom processing logic goes here

    // Standard URL pattern for "New"
    String keyPrefix = MyCustomObject__c.SObjectType.getDescribe().getKeyPrefix();
    PageReference newPr = new PageReference('/' + keyPrefix + '/e');

    // Preserve any query string parameters
    Map<String, String> oldParams = ApexPages.currentPage().getParameters();
    Map<String, String> newParams = newPr.getParameters();
    newParams.putAll(oldParams);

    // Avoid an infinite loop - go to default UI not back to this controller
    newParams.put('nooverride', '1');

    return newPr;
  }
}

This works well when “Save” is clicked in the resulting default UI page. But when “Save & New” is clicked, this (confusing) error message results:

Error: Invalid Data.
Review all error messages below to correct your data.
The page you submitted was invalid for your session. Please click Save again to confirm your change.

After much stripping back of the controller code and comparisons with the typical URL patterns, the problem turns out to be that in the “Save & New” case a save_new parameter is added for part of the cycle but that should not be present in the URL of the final page that is presented.

Adding this line of code (or otherwise making sure the parameter is not present in the final URL) fixes the problem:

    newParams.remove('save_new');

How much harm can one line of code do?

Suppose you are developing a feature that does programmatic manipulation of sharing rules. You go ahead and set the sharing default for your CustomObject to other than “Public Read/Write” in your development org. This automatically creates the CustomObject__Share object to go with your CustomObject__c and so allows you to add a class with a signature such as this:

global class Xyz {
    global void doSomething(List<CustomObject__Share> shares) {
        // ...
    }
}

And then you go ahead and create a managed released package.

The good news is that you now have a managed package that works well for customers who want to configure sharing rules. But the bad news is that you have made sharing rules mandatory for all your customers: the package cannot be installed unless the sharing default for CustomObject is set to other than “Public Read/Write” in the org the package is deployed to.

Woops.

So you go to fix this. The signature you would like to use instead is:

global class Xyz {
    global void doSomething(List<SObject> shares) {
        // ...
    }
}

But then you realize that global method signatures can’t be changed once included in a managed released package. Using @deprecated doesn’t help in this case: it is the presence of CustomObject__Sharing in the signature (whether the method is marked as deprecated or not) that creates the problem. Bottom line is that you are stuck.

Woops WTF.

Be careful what you put in the signature of global methods.