Tag Archives: apex

Gnarly SFDC Error Messages – Personal Experience

This post will be updated from time to time and serves as a knowledge base for how not-so-obvious SFDC error messages might be resolved. A running list based on personal experience

INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY

  1. Public read-only sharing model on Parent__c
  2. Parent inserted by user A
  3. User B inserts/updates child record within master-detail relationship to Parent__c
  4. No sharing rule extending write access to User B for Parent__c. For example, testmethod user not in a role that has Parent__c shared to.
  1. Private OWD sharing model on Parent__c. Criteria-based sharing to extend owners of Parent__c to user B’s role
  2. Apex testmethod context
  3. User A inserts Parent
  4. User B inserts child record with lookup to Parent__c
  5. Apex testmethods do not honor criteria-based sharing. Workaround is to insert Parent and Child by same user
  1. Object X has lookup relationship to Object Y
  2. When Object X is inserted or updated, if the value of X.lookup_y__c is to an id that is valid for Object Y’s type but does not exist in the database
  3. Typical examples occur when cloning X from something else or sync’ing X from a different SFDC org
  1. Detail Object D is detail to master Object M
  2. When operation is via API or APEX
  3. When Object D is inserted without a value for the master’s ID field. (example: insert OrderItem without OrderItem.OrderId
  4. Typical example is a misconfigured middleware operation wherein you were expecting to do an update but configured as an insert. Will not be caught in standard UI as the master field is required on the page layout.

INVALID_CROSS_REFERENCE_KEY

  1. Insertion of an OpportunityLineItem, QuoteItem, or OrderItem
  2. PricebookEntryID references a PricebookEntry for Pricebook P
  3. Parent Opportunity, Quote, or Order has Pricebook2Id of Pricebook Q

INVALID_PARTNER_NETWORK_STATUS
Either:

  1. Active connection to partner org
  2. sObjects not published to partner org
  3. You insert those sObjects in your code/testmethods

or …

  1. Active connection to partner org
  2. sObjects published to partner org
  3. sObjects not subscribed within partner org
  4. You insert those sObjects in your code/testmethods

or …

  1. Active connection to partner org
  2. Sharing a child record (like an Opportunity)
  3. But, the parent record (like an Account) has not yet been accepted

INVALID_SSO_GATEWAY_URL

  1. Running user has no Federation Id and
  2. Running user’s profile is defined with ‘Is Single Signon Enabled’ as true

You cannot unset the role on the owner of a portal an account which has partner users.

  1. You are trying to remove (not replace) the role from a User u
  2. That user is the Owner of one or more Accounts that have been enabled as Partner Accounts. (Using SFDC Partner Portal)

The solution is to find all such Accounts owned by User u that are Partner Accounts. You can use a List View to do this, filtering on Partner Account = true. In our use case, we were no longer using Partner Portal and were doing role cleanup so simply disabling each Account as a ‘partner account’ using the Manage External Accounts button on the Account detail page was all that was required. If said accounts are still valid as Partner Accounts, change the owner to some other owner that does have a role.

ANT build: MapHighlightAction is not a standard action and cannot be overridden.

  1. This is an object with address fields like Contact
  2. Your dev sandbox does not have Google Maps enabled – Setup | Customize | Maps and Locations | Settings but your target deployment org does have Google Maps enabled.
  3. The metadata for the dev sandbox SObject (e.g. Contact) will include lines as shown below

     <actionOverrides>
        <actionName>MapHighlightAction</actionName>
        <type>Default</type>
    </actionOverrides>

What seemed to be happening is that when you deploy an SObject like Contact from dev sandbox without Google Maps enabled, if the target sandbox/prod org does have Google Maps enabled, the above lines generate the error MapHighlightAction is not a standard action and cannot be overridden.

If you enable Google Maps in your dev sandbox, and examine the Contact.obj metadata, the above lines are removed and, assuming your target deployment org also has Google Maps enabled, the deployment will succeed. We encountered this issue when we did our first dev sandbox to fullcopy sandbox deployment using an SObject (Contact) with address fields. The dev sandbox was created pre-V33 but upgraded to V34 whereas the fullcopy sandbox was created V33 and also upgraded to V34. V33 was when Google Maps was introduced.

ANT build: Required field is missing

  1. You have an Eclipse project with package.xml against your dev sandbox at Version x
  2. Your Ant build against staging sandbox uses package.xml at Version x+n, n> 0
  3. You run ant from your local GIT repo (containing your metadata files, like xxx.workflow) against your staging sandbox, using the staging sandbox package.xml

What seems to happen is the package.xml file in your dev sandbox Eclipse is defaulted to the earliest version of any class in your project. In my case it was V15.0. However, the Ant build against Staging sandbox used package.xml at V30.0. When you get metadata from the dev sandbox into Eclipse, the metadata files are presented according to what is supported by the dev sandbox’s package.xml version #. Using V15.0, the metadata for xxx.workflow files is no longer compatible with V30.0 (email alerts need to have descriptions, tasks need to have subjects).

The solution is to update the package.xml file in the dev sandbox to the same version as used in the ant build. Then refresh the src tree in the Eclipse dev project

There is already a Child Relationship named Foos on Bar

  1. You are doing a ChangeSet deployment of a lookup custom field Bar__c on Sobject X
  2. The parent SObject (Foo) of the lookup field Bar__c has a child relationship named Foos (Foos__r)
  3. You haven’t changed anything on X or changed anything about the lookup field or its child relationship name. In sandbox there are no duplicates. In PROD, the Sobject X doesn’t even exist.

A likely cause of this is that in PROD, you have a deleted SObject X with field Bar__c. But the deleted object is within the 45 day window before it is permanently deleted by Salesforce. Hence, when the changeset comes through, SFDC thinks there is already the child relationship (albeit in a deleted sobject) and considers your change to be a duplicate.

The solution is to Erase the deleted Sobject in the target org (PROD) and retry the deployment of the Change Set.

Salesforce to Salesforce Using REST – Part II

A bit of a temporary setback here.
I naively thought that whatever you could do in the SFDC SOAP API you could do in the REST API. Specifically, that it would be possible for APEX code in the source product catalog org to do mass inserts, updates, or upserts in the target org. NOPE.

The REST API only allows Create-Update-Delete operations on single resources – that is, single records. You can query (Read) to your heart’s content over many records. Teach me to not fully read the documentation.

Alternatives

  1. I could have the source org use the SOAP API. I looked into this and it is kind of a heavy-weight solution wherein you generate a massive Apex class from the partner or Enterprise WSDL. I tried this and realized that the Enterprise WSDL was too large to import and thus would need to be trimmed before I could generate the APEX bindings to it. This would mean maintenance issues. The Partner WSDL isn’t strongly typed and thus is more trouble than it’s worth to work with.
  2. I could use the SFDC Bulk API from the source org. This has other issues notably the input has to be either XML or CSV so I’d need to do conversions and, more importantly, it is asynchronous making the Visualforce page flow control complex with polling. Yuck.
  3. I could write a custom REST service that would be deployed on all target orgs that would handle the upserts through its own logic. This looked like the best way forward as the source org code would be simple – create a payload in the POST body for the Product2, PricebookEntry, and Pricebook2 SObjects. The target org custom REST service would figure out what needed to be done, cross-referencing source org data with existing target org data matching on Product2.ProductCode.

In many ways, option 3 is best as it will make testing easier through separation of responsibilities and much, much simpler Test Mock classes on the source org’s Apex class.

Salesforce to Salesforce Using REST – Part I

Here was the business problem:

Migrate Product2-PricebookEntry-Pricebook2 data from a sandbox to staging sandbox and then onto production.

My previous brute force solution was to use the Excel Connector, querying rows from the Pricebook2, PricebookEntry, and Product2 tables using a connection to the sandbox, then successively inserting rows into the target org while replacing ID fields in the upload PricebookEntry dataset to match the just uploaded Pricebook2 and Product2 datasets. (PricebookEntry is a junction record between Pricebook2 and Product2). This involved Excel VLOOKUP functions and lots of care to make sure that I didn’t reorder rows by accident.  Doing this once wasn’t so bad but the business changes the product line regularly and I was finding this tedious.

Solution Outline

Why not build an admin VF page in my org that could read from my source dataset sandbox and insert rows into the target sandbox/prod org?  To make this happen, the Visualforce controller code would need to be able to do SOQL and DML against a different org.  The way to do this is by exploiting the SFDC REST API and Apex HTTP Callouts.

The SFDC REST API documentation is written assuming that the calling application is not a Salesforce org but there is no restriction that this be so.

Step 1 – AuthenticationOAUTH Authentication SFDC to SFDC

The relevant SFDC REST API authentication documentation is here.  I chose the username-password method as I’m the admin and would use this to get authentication on the target orgs using my own username and passwords on those orgs.  I also opted for OAuth 2.0 authentication as I wanted to try this rather than using sessionIds.

public with sharing class HttpRest {

	//	Class to handle HTTP Rest calls to other SFDC instances
	//
	//	[HTTP-00]	- Reached limit on callouts
	//	[HTTP-01]	- Unable to get OAuth 2.0 token from remote SFDC
	//	[HTTP-02]	- Error in SOQL REST query

	String	accessToken;					// OAuth 2.0 access token
	String	sfdcInstanceUrl;				// Endpoint URL for SFDC instance
						
	private HttpResponse send(String uri,String httpMethod) {
		return send(uri,httpMethod,null);
	}
		
	private HttpResponse send(String uri, String httpMethod, String body) {
		
		if (Limits.getCallouts() == Limits.getLimitCallouts())
			throw new MyException('[HTTP-00] Callout limit: ' + Limits.getCallouts() + ' reached. No more callouts permitted.');
		Http		h		= new Http();
		HttpRequest	hRqst	= new HttpRequest();
		hRqst.setEndpoint(uri);						// caller provides, this will be a REST resource
		hRqst.setMethod(httpMethod);				// caller provides
		hRqst.setTimeout(6000);	
		if (body != null) 
			hRqst.setBody(body);					// caller provides
		if (this.accessToken != null)				// REST requires using the token, once obtained for each request
			hRqst.setHeader('Authorization','Bearer ' + this.accessToken);
		return h.send(hRqst);					// make the callout
	}	
	//	----------------------------------------------------------------------
    //	authenticateByUserNamePassword		: Returns a map of <String,String> of the OAuth 2.0 access token; required before REST calls on SFDC instances can be made 
    //	----------------------------------------------------------------------
    public void authenticateByUserNamePassword(String consumerKey, String consumerSecret, String uName, String uPassword, Boolean isSandbox) {
    	// Reference documentation can be found in the REST API Guide, section: 'Understanding the Username-Password OAuth Authentication Flow'
    	// OAuth 2.0 token is obtained from endpoints:
    	//	PROD orgs	: https://login.salesforce.com/services/oauth2/token
    	//	SANDBOX orgs: https://test.salesforce.com/services/oauth2/token
    	
 		//	OAuth 2.0 access token contains these name/values:
 		//		access_token		: used in subsequent REST calls
 		//		instance_url		: to form the REST URI
 		//		id					: identifies end user
 		//		issued_at			: When signature was created
 		//		signature			: HMAC-SHA256 signature signed with private key - can be used to verify the instance_url	 

		String uri 			= 'https://' + (isSandbox ? 'test' : 'login') + '.salesforce.com/services/oauth2/token';
		String clientId 	= EncodingUtil.urlEncode(consumerKey,'UTF-8');
		String clientSecret = EncodingUtil.urlEncode(consumerSecret,'UTF-8');
		String username		= EncodingUtil.urlEncode(uName,'UTF-8');
		String password		= EncodingUtil.urlEncode(uPassword,'UTF-8');

		String body =	'grant_type=password&client_id=' + clientId + 
						'&client_secret=' + clientSecret +
						'&username=' + username + 
						'&password=' + password; 

		HttpResponse hRes = this.send(uri,'POST',body);
		if (hRes.getStatusCode() != 200) 
			throw new MyException('[HTTP-01] OAuth 2.0 access token request error. Verify username, password, consumer key, consumer secret, isSandbox?  StatusCode=' +
												 hRes.getStatusCode() + ' statusMsg=' + hRes.getStatus());
			
		
		System.debug(FlowControl.getLogLevel(),'response body =\n' + hRes.getBody());
		

		Map<String,String> res = (Map<String,String>) JSON.deserialize(hRes.getBody(),Map<String,String>.class);

		this.accessToken		= res.get('access_token');		// remember these for subsequent calls
		this.sfdcInstanceUrl 	= res.get('instance_url');
		
		
    }


    //	-----------------------------------------------------------------------
    //	doSoqlQuery: Executes a REST query on a remote SFDC and returns a list of SObjects
    //	-----------------------------------------------------------------------
    public List<SObject> doSoqlQuery(String query) {
    	List<Sobject>	res;		
		PageReference 	urlPg	= new PageReference(this.sfdcInstanceUrl + '/services/data/v29.0/query');
		urlPg.getParameters().put('q',query); 

		String uri 				= urlPg.getUrl();				// let APEX do the URL encoding of the parms as necessary
		HttpResponse hRes = this.send(uri,'GET');
		if (hRes.getStatusCode() != 200) 
			throw new MyException('[HTTP-02] Error in query ' + uri + ' StatusCode=' +
												 hRes.getStatusCode() + ' statusMsg=' + hRes.getStatus());
		
		// Response body comes back as:
		// {"totalSize":10,
		//	"done":true,
		//	"records":[
		//				{"attributes":{
		//					"type"	: "the sobject",
		//					"url"	: "/services/data/v29.0/sobjects/the sobject/the id"
		//				},
		//				"field0 in query"	: "value of field 0",
		//				"field1 in query"	: "value of field1",
		//				...},
		//				next record ...
		//				]
		//	}
		JSONParser jp = JSON.createParser(hRes.getBody());
		do{
			jp.nextToken();
		} while(jp.hasCurrentToken() && !'records'.equals(jp.getCurrentName()));
		jp.nextToken();  // token is 'records'
		res = (List<SObject>) jp.readValueAs(List<SObject>.class);		// Let caller cast to specific SObject
		return res;
    }
 
 
 	//	--------------------------------------------------------------
    // 	TEST METHODS - MOCKS
    //	---------------------------------------------------------------	
	public class authenticateByUserNamePasswordMock implements HttpCalloutMock {
    	
    	Boolean	isMockResponseSuccessful;
    	
    	public authenticateByUserNamePasswordMock(Boolean isMockResponseSuccessful) {
    		this.isMockResponseSuccessful	= isMockResponseSuccessful;
    	}
    	
    	public HttpResponse respond(HttpRequest rqst) {
    		HttpResponse hResp		= new HttpResponse();
    		if (this.isMockResponseSuccessful) {
    			hResp.setStatusCode(200);
    			hResp.setBody('{' +			// data copied from SFDC example
    							' "id":"https://login.salesforce.com/id/00Dx0000000BV7z/005x00000012Q9P", ' +
								' "issued_at":"1278448832702",' +
								' "instance_url": "https://na1.salesforce.com",' +
								' "signature":"0CmxinZir53Yex7nE0TD+zMpvIWYGb/bdJh6XfOH6EQ=",' +
								' "access_token": "00Dx0000000BV7z!AR8AQAxo9UfVkh8AlV0Gomt9Czx9LjHnSSpwBMmbRcgKFmxOtvxjTrKW19ye6PE3Ds1eQz3z8jr3W7_VbWmEu4Q8TVGSTHxs"' 
							+'}');			
    		}
    		else {
    			hResp.setStatusCode(400);
    			hResp.setStatus('Bad request');
    		}
    		return hResp;
    	}
    }
    
    public class doSoqlQueryMock implements HttpCalloutMock {
    	
    	Boolean	isMockResponseSuccessful;
    	
    	public doSoqlQueryMock(Boolean isMockResponseSuccessful) {
    		this.isMockResponseSuccessful	= isMockResponseSuccessful;
    	}
    	
    	public HttpResponse respond(HttpRequest rqst) {
    		HttpResponse hResp		= new HttpResponse();
    		if (this.isMockResponseSuccessful) {
    			hResp.setStatusCode(200);
    			hResp.setBody('{' +			// data copied from SFDC example
    							' "totalSize": 2,' 		+ 
								' "done" 	: true,' 	+
								' "records" : ['		+
								'              { 	"attributes" 	: {' +
								'										"type"			: "Account",' +
								'										"url"			: "/services/data/v29.0/sobjects/Account/00id"' +
								'									},' +
								'				"ID"	: "00id"' +
								'				},' +
								'              { 	"attributes" 	: {' +
								'										"type"			: "Account",' +
								'										"url"			: "/services/data/v29.0/sobjects/Account/01id"' +
								'									},' +
								'				"ID"	: "01id"' +
								'				}' +
								'			]' +
								'}');			
    		}
    		else {
    			hResp.setStatusCode(404);
    			hResp.setStatus('Not found');
    		}
    		return hResp;
    	}
    }
    
    
   	//	--------------------------------------------------------------
    // 	TEST METHODS - tests
    //	--------------------------------------------------------------- 
	@isTest
    private static void testAuthenticateByUserNamePassword() {
    	HttpRest hr = new HttpRest();
    	try {
    		Test.setMock(HttpCalloutMock.class, new authenticateByUserNamePasswordMock(true));	
    		hr.authenticateByUserNamePassword('somekey','somesecret','someusername@somedomain','somepw+token',false);
    		System.assertEquals('00Dx0000000BV7z!AR8AQAxo9UfVkh8AlV0Gomt9Czx9LjHnSSpwBMmbRcgKFmxOtvxjTrKW19ye6PE3Ds1eQz3z8jr3W7_VbWmEu4Q8TVGSTHxs',hr.accessToken);
    		System.assertEquals('https://na1.salesforce.com',hr.sfdcInstanceUrl);
    	}
    	catch (Exception e) {System.assert(false,'shouldnt. Exception:' + e.getMessage() + e.getStackTraceString());}
    	try {
    		Test.setMock(HttpCalloutMock.class, new authenticateByUserNamePasswordMock(false));
    		hr.authenticateByUserNamePassword('somekey','somesecret','someusername@somedomain','somepw+token',false);
    		System.assert(false,'shouldnt happen, mock should produce error');
    	}
    	catch (Exception e) {System.assert(e.getMessage().contains('[HTTP-01]'),e.getMessage());}
    }

	@isTest
    private static void testDoSoqlQuery() {
    	HttpRest hr = new HttpRest();
    	List<Sobject>	sobjResList;
    	try {
    		Test.setMock(HttpCalloutMock.class, new doSoqlQueryMock(true));	
    		sObjResList = hr.doSoqlQuery('select id from Account');
    		System.assertEquals(2,sObjResList.size());
    	}
    	catch (Exception e) {System.assert(false,'shouldnt. Exception:' + e.getMessage() + e.getStackTraceString());}
    	try {
    		Test.setMock(HttpCalloutMock.class, new doSoqlQueryMock(false));
    		sObjResList = hr.doSoqlQuery('select id from Account');
    		System.assert(false,'shouldnt happen, mock should produce error');
    	}
    	catch (Exception e) {System.assert(e.getMessage().contains('[HTTP-02]'),e.getMessage());}
    }


}

Implementation Notes

  1. You’ll need your own Exception class for this to compile. The example calls it MyException but I usually name these after the project or business unit wherein my code is deployed.
  2. Note that method authenticateByUserNamePassword takes five arguments. You will need to set up the consumerKey and consumerSecret on the target org using Setup | Develop | Remote Access. Starting in V29.0, this is now called a ‘Connected App’.  Your source org will need access to these values – perhaps through custom settings, directly-entered into your VF page, or for more security, as encrypted fields in an custom SObject.
  3. Your source org will need to Setup | Security Controls | Remote Site Settings for both the OAuth 2.0 authentication access token provider(s) as well as the target instance(s).
Connected App Setup in the target org

There are several OAuth scopes available to you. I chose ‘Access and manage your data (api)’ as that met my requirements.ConnectedAppSetup

Remote Site settings in the source org

In my source org, I set up four Remote Sites – two for the production and sandbox OAuth 2.0 access token providers and one each for the target PROD and target sandbox.
remoteSites

Good APEX Naming Conventions

To inaugurate the blog, I’ll start with something prosaic but one where I saved myself a lot of grief over time as I built up large APEX systems. Naming conventions are of course a matter of personal preference. I’ve seen a lot of posts asking for help on the SFDC Apex Forum and many times the poster’s naming conventions get in the way of those trying to assist. Professional programmers will already know all this stuff, so this might be more appropriate for Apex newbies and ‘toddlers’.

Class and method names

  • Follow the SFDC conventions. Your code is a mix of SFDC classes and methods so follow the idiom. Class names are initialcaps.
  • Method names are initial lowercase. Don’t use underscores. If the method returns a Boolean, make the method name an assertion statement
public class Foo{
  public Boolean isClosed() {
   // some condition that returns true if something is true, else false
  }
}

Consistency in abbreviations

I use a consistent shorthand for my APEX variables (and by extension Visualforce var= attributes).

  • Variables that reference Accounts start with ‘a’, Opportunities with ‘o’, Contracts with ‘c’, Quotes with ‘q’, QuoteItems with ‘qi’ and so on.
  • Where there is a first letter conflict, I stick to something clear and easily remembered such as ‘cs’ for Case or ‘attach’ for Attachments
  • Custom Objects would use a shorthand made up the first letters of each word (or maybe syllable) such as ‘pcr’ for Product_Config_Rule__c

Consistency in references to IDs

  • IDs are your friend as they can be used to reference maps, query for SObjects, and are globally unique.  Use your variable names to ‘type’ your Ids
  • Thus, aId is an Account Id, qiId is a quote Item Id
Account a;  // the account being worked on
Account aOld; // the prior version of the Account from Trigger.old
ID aId;  // an account Id

Consistency in collection variable names

  • You are constantly coding in the bulk idiom or handling result lists from queries. Because SFDC provides different instance methods for lists, sets, and maps and because each collection type serves a different purpose, it greatly improves code readability when the variable name makes it clear whether it is a list, set or map.  I always append my collection variable names with either ‘List’, ‘Set’, or ‘Map’. It also helps you code as you operate on the collection – as you type the variable name, you remind yourself of how it works (for example, maps aren’t in any sort order).
  • Since lists are often created to do DML or are the results of queries, add a common shorthand to the variable name as to purpose. For example ‘ins’ for a insert list, ‘upd’ for an update list, or ‘res’ for a query result list

Good names

List<Account> aInsList;  // a list of Accounts to insert
List<Opportunity] oUpdList;  // a list of Opportunities to update
List<Contract> cReslist = [select id from Contract where name like 'Foo%'];  // query result

Set<ID> aIdSet;  // a set of unique accountIds

Map<ID,Case> csIdToCaseMap; // a map of Case Ids to Case SObjects
Map<ID,List<Quote>> oIdToQuoteListMap;  // Map of Oppo ID to a list of Quotes

// Map of Case Id to Case with related lists fetched from a query
Map<ID,Case> csIdToCaseWRelListMap =
  new Map<ID,Case> ([select id, accountId, caseNumber, closedDate, contactId, subject, type,
		     (select id, commentBody, createdDate, createdById, isPublished
			     from CaseComments order by createdDate),
		     (select id, messageDate, subject
			     from EmailMessages order by MessageDate asc)
		    from Case where id IN :csIdSet]);

Avoid doing this…

List<Account> accts;  // when referenced in the code, is it a list, set or map?
List<Opportunity] updates;  // updates of what sObject?

Set<ID> ids;  // a set of Ids to what sObject?