Tuesday, December 22, 2015

A Tale of Two Triggers

The following is a cautionary tale about the importance of having only one* trigger per object type. It is based on actual events. Only the names, apex code, and events have been changed.

Imagine you have a trigger something like the one that appears below, along with its associated support Apex class.

Don't worry too much about reading all the code. Just know that the trigger fires after insert or update of an Opportunity record to do some processing. The first order of business is to ensure that the trigger hasn't already run in the current context due to other triggers that may be present in the org. We want to prevent multiple calls to the trigger in one transaction and the potential for trigger recursion. This is achieved via a static set of Opportunity Ids. Once we've processed a given Opportunity Id and put it in the set there is no need to revisit it again in the current transaction.

trigger MyProduct_Opportunity on Opportunity (after insert, after update) {

    OppTrg_MyProductIntegration productIntegration = new OppTrg_MyProductIntegration ();
    if (trigger.isAfter) {
        productIntegration.handleAfter(trigger.new, trigger.oldMap, trigger.isInsert, trigger.isUpdate);
    }

}
public with sharing class OppTrg_MyProductIntegration {
        private static Set visitedOpportunityIds = new Set();

    public void handleAfter(List triggerNew, Map triggerOldMap, boolean triggerIsInsert, boolean triggerIsUpdate) {

        List tasksToInsert = new List();
        
        for(Opportunity opp : triggerNew) {

            if(visitedOpportunityIds.contains(opp.Id)) {
            System.debug(LoggingLevel.Debug, 'OppTrg_MyProductIntegration.handleAfter skipping OpportunityId: ' + opp.Id + ' triggerIsInsert:' + triggerIsInsert + ' triggerIsUpdate:' + triggerIsUpdate);
            continue;
        } else {
            visitedOpportunityIds.add(opp.Id);
            System.debug(LoggingLevel.Debug, 'OppTrg_MyProductIntegration.handleAfter processing OpportunityId: ' + opp.Id + ' triggerIsInsert:' + triggerIsInsert + ' triggerIsUpdate:' + triggerIsUpdate);
        }

            // Do further processing using opp.Id
            boolean doStuff = triggerIsInsert;
            // If it's an update, check that certain fields have changed
            if(triggerIsUpdate) {
                Opportunity beforeUpdate = triggerOldMap.get(opp.Id);
                // We only need to do something if the Amount has changed
                doStuff = beforeUpdate.Amount != opp.Amount;
            }
            // Otherwise, if it's an insert, proceed with the additional processing
            
            System.debug(LoggingLevel.Info, 'OppTrg_MyProductIntegration doStuff:' + doStuff);
            if(doStuff) {
                //System.assertNotEquals(5000, opp.Amount, 'OppTrg_MyProductIntegration deliberate assertion failure doing stuff');
                Task mockTaskToIndicateStuffHappened = new Task();
                mockTaskToIndicateStuffHappened.WhatId = opp.Id;
             mockTaskToIndicateStuffHappened.Subject = 'Follow Up Test Task';
                tasksToInsert.add(mockTaskToIndicateStuffHappened);
                
            }

        }
        
        insert tasksToInsert;

    }
}

Now the fun begins. Users report that the desired functionality from the after insert trigger code isn't always occurring in production. The first step is to acquire the associated debug log to see what is going on.

Here is an extract from the DEBUG logging messages for the OppTrg_MyProductIntegration class when the assertion fails:

Time            Event            Info 1  Info 2   Message
14:16:31.573 DML_BEGIN  [26]  Op:Insert Type:Opportunity|Rows:1
14:16:31.714 USER_DEBUG  [15]  DEBUG  OppTrg_MyProductIntegration.handleAfter processing OpportunityId: 0067000000c7d3jAAA triggerIsInsert:false triggerIsUpdate:true
14:16:31.717 USER_DEBUG  [11]  DEBUG  OppTrg_MyProductIntegration.handleAfter skipping OpportunityId: 0067000000c7d3jAAA triggerIsInsert:true triggerIsUpdate:false
14:16:31.721 EXCEPTION_THROWN [29]   System.AssertException: Assertion Failed: Expected: 1, Actual: 0

Look closely at the messages, the after insert/update trigger called MyProduct_Opportunity first occurs for update, and secondly for insert. The record was updated before it was inserted!

What's really going on here? The extended debug log shows the cause.

Time            Event              In1  Info 2   Message
14:16:31.573 DML_BEGIN    [26] Op:Insert Type:Opportunity|Rows:1
14:16:31.600 CODE_UNIT_STARTED  [EX] 01q70000000TiLP DFB.OpportunityAfterInsertTest on Opportunity trigger event AfterInsert for [0067000000c7d3j]
14:16:31.601 SOQL_EXECUTE_BEGIN [17] Aggregations:0 SELECT Id FROM Opportunity WHERE Id IN :tmpVar1
14:16:31.604 SOQL_EXECUTE_END   [17] Rows:1
14:16:31.605 DML_BEGIN    [21] Op:Update Type:Opportunity|Rows:1
14:16:31.712 CODE_UNIT_STARTED  [EX] 01q70000000TiLK DFB.MyProduct_Opportunity on Opportunity trigger event AfterUpdate for [0067000000c7d3j]
14:16:31.714 USER_DEBUG    [15] DEBUG  OppTrg_MyProductIntegration.handleAfter processing OpportunityId: 0067000000c7d3jAAA triggerIsInsert:false triggerIsUpdate:true
14:16:31.714 USER_DEBUG    [28] INFO  OppTrg_MyProductIntegration doStuff:false
14:16:31.714 CODE_UNIT_FINISHED   DFB.MyProduct_Opportunity on Opportunity trigger event AfterUpdate for [0067000000c7d3j]
14:16:31.714 DML_END       [21]
14:16:31.717 USER_DEBUG    [11] DEBUG  OppTrg_MyProductIntegration.handleAfter skipping OpportunityId: 0067000000c7d3jAAA triggerIsInsert:true triggerIsUpdate:false
14:16:31.717 DML_END     [26]
14:16:31.717 SOQL_EXECUTE_BEGIN [28]   Aggregations:0 SELECT Id FROM Task WHERE WhatId = :tmpVar1
14:16:31.720 SOQL_EXECUTE_END   [28]   Rows:0
14:16:31.721 EXCEPTION_THROWN   [29]   System.AssertException: Assertion Failed: Expected: 1, Actual: 0

Going carefully through the log, there is another rogue after insert trigger in there that is resulting in an update on the record that was just inserted. This subsequent update causes our trigger of interest to execute first in an update context, before returning to execute for the original insert. The Set of processed Id's is tripping us up here. When the trigger first fires for update the ID isn't in the Set, so the fields are checked for a changed value. Being an update on another field the check doesn't pass, and the additional processing is skipped.

trigger OpportunityAfterInsertTest on Opportunity (after insert) {
     
    List toUpdate = new List();
    for(Opportunity opp : trigger.new) {
        if(opp.Description == 'Hello') {
            toUpdate.add(opp.Id);
        }
    }
    
    if(toUpdate.size() > 0) {
        List opps = [Select Id from Opportunity where Id in :toUpdate];
        for(Opportunity opp : opps) {
            opp.Description = 'World';    
        }  
        update opps; 
    }
}
@IsTest
public class OppTrg_MyProductIntegration_Test {

    @IsTest
    public static void StuffExpectedToHappen(){
        Opportunity opp = new Opportunity();
        opp.Name = 'Test';
        opp.Description = 'FooBar';
        opp.StageName = 'Closed Won';
        opp.CloseDate = DateTime.now().date();
        opp.Amount = 5000;
        insert opp;
        
        List tasksInsertedForNewOpp = [Select Id from Task where WhatId = :opp.Id];
        System.assertEquals(1, tasksInsertedForNewOpp.size());
    }
    
    @IsTest
    public static void WhyDidntStuffHappen(){
        Opportunity opp = new Opportunity();
        opp.Name = 'Test';
        opp.Description = 'Hello';
        opp.StageName = 'Closed Won';
        opp.CloseDate = DateTime.now().date();
        opp.Amount = 5000;
        insert opp;
        
        List tasksInsertedForNewOpp = [Select Id from Task where WhatId = :opp.Id];
        System.assertEquals(1, tasksInsertedForNewOpp.size());
    }
    
}

Which brings us back to the moral of the story, the only way to get predictable trigger ordering is to have one trigger per object type that passes off to other classes in a defined order. This is sometimes easier said than done. Multiple managed packages can all introduce their own set of triggers.

It terms of fixing the example triggers above, we can assume that the action should always occur in a trigger context. Basically bypass the Set check unless it is an update operation.

See also:

Friday, December 18, 2015

Trailhead - Build a Battle Station App

How does one go about building a moon sized death star? That's a lot people and supplies to keep track of for a project with a budget over 1,000,000,000,000 galactic credits. Putting together a system to help manage the build is going to be a major project in and of itself. Or is it?

Build a Battle Station App

Lucky for us the Salesforce Trailhead team has the timely launch of the Build a Battle Station App Trailhead project.

Highlights you'll learn from completing this project:

  • How do you keep the Exhaust Port Inspectors from dropping the ball again and letting magic space wizards drop bombs to the core.
  • How to track all the required supplies. Tractor beams, ultra fast hydraulic units for the doors. Don't forget the light bulbs and toilet paper. Handling the guard rail shortage will be left as an exercise to the readers.
  • Get a quick summary of how many people are actually working on the project.
  • Use Lightning Process Builder and Chatter to announce when you've got a fully armed and operational death star!
  • How to help your fat fingered boss use the mobile app.
  • Test the mobile app in Chrome using the Salesforce1 Simulator app.

As an added bonus, there is also a competition running for those who finish the badge by 2015-12-31 11:59pm PST

Just noticed Model Complex Products with Hierarchical Assets in the Spring `16 release notes. That could be useful in this project.


Apex Integration Services

There is also the new Apex Integration Services module. If you can get past the SOAP bashing there is some good information in there.

How's this for clicks not code? (Or at least minimal code to meet the ceremony required by the challenge)

  1. Take the URL for the SOAP WSDL from the Apex SOAP Callouts Challenge. No need to save it to disk first.
  2. Put it through the FuseIT SFDC Explorer custom WSDL2Apex implementation. Call the output ParkService and check the option to generate test cases.
  3. Rename the generated mock class "ParkServiceMockImpl"
  4. Rename the generated test class "ParkLocatorTest"
  5. Make the ParkLocator class with the static country method to call the generated class.
  6. In ParkLocatorTest duplicate one of the assertions to also call the ParkLocator.country method to give the required coverage.
  7. Add the remote site setting for the callout URL.
  8. Run the generated ParkLocatorTest test case.
  9. Smugly pass the challenge test

OK, maybe not so smugly. There are more steps above than I care for, but most of those are to satisfy the ceremony of the challenge with regards to naming etc... There is also something odd about this WSDL the is throwing off the generated apex_schema_type_info members and requires the elementFormDefault="qualified" boolean (second to last one) to be manually changed from 'true' to 'false'.

Still, show me the same level of initial setup from a REST API in Apex and I'd be impressed.

Friday, November 27, 2015

Rejecting the Salesforce CKEditor and substituting your own.

My biggest pain point with the Salesforce developer forums is the CKEditor. In particular, how it interferes with the browsers native spell checker. My spelling skills aren't always the best, but are good enough when propped up by the red squiggles. The problem is compounded by not being able to edit existing posts. Your spelling mistakes can now haunt you forever unless you are willing to delete a post.

There is also the general problem with WYSIWYG editors in that the underlying formatting can get unwieldy. Copying and pasting content from multiple sources is a quagmire of mismatched HTML. It's what you can't see that is important to the formatting.

Also, good luck to you if you've written an answer that is a contender for a Nobel Prize in Literature only to lose it all due to an expired session. Maybe you'll get lucky and can browse back to recover it. More likely it is lost forever and you'll just knock out a one or two line reply instead. Now devoid of formatting, images and hyperlinks. Because why bother doing it all again?

Status Quo

Right now I've got a few techniques for dealing with the CKEditor used in the success forum.

  • Write everything somewhere else where a spellchecker is available and then copy and paste it over at the last minute. Adding in any links, images, and formatting just before submission.
  • Use the browsers developer tools to manipulate the HTML source that the CKEditor is working from.
  • Before submitting an answer, copy the content out just in case there is a session issue and the form doesn't submit correctly. You'll likely lose formatting, links, etc..., but you'll keep your prose.

The Hackening

I did a little spelunking into the page source and network traffic. You can see that the CKEditor is currently configured with a request to https://developer.salesforce.com/forums/ckeditor/ckeditor-4.x/rel/sfdc-config.js?t=4.4.6.4, It's this config that toggles various functions in the editor.

Of particular interest here is the a.toolbar_ServiceCommunity array. This configures the toolbar buttons that are available.

    a.toolbar_ServiceCommunity = [
        ["Undo", "Redo"],
        ["Bold", "Italic", "Underline",
            "Strike"
        ],
        ["Link", "sfdcImage", "sfdcCodeBlock"],
        ["JustifyLeft", "JustifyCenter", "JustifyRight"],
        ["BulletedList", "NumberedList", "Indent", "Outdent"]
    ];

Now that we know how the CKEditor is being configured we can start hacking at it. Lets see if we can get the native source view going.

Injecting your own configuration

This option is perhaps a bit heavy handed. The basic idea is to use a custom Chrome extension to intercept the HTTP requests to https://developer.salesforce.com/forums/ckeditor/ckeditor-4.x/rel/sfdc-config.js?t=4.4.6.4 and redirect the browser to config content that I control.

Parts of the extension

manifest.json

Configures capturing web requests from https://developer.salesforce.com

background.js

Listens for requests to https://developer.salesforce.com/forums/ckeditor/ckeditor-4.x/rel/sfdc-config.js?t=4.4.6.4 and redirects them to the static resource config.

See chrome.webRequest

config.js

The replacement for the standard Salesforce config.

The main problem with redirecting the browser to pull content alternative content is that it won't easily accept something from another domain.

This can be overcome by hosting the replacement configuration as a static resource in an org of your choosing. I used by developer edition org, as I often have an active session there.

Load the config.js Javascript into an Org of your choice as a static resource. I'd encourage you to modify your own version rather that taking the version I've got here. Who knows what sort of evil Javascript could be hidden away in there. Better to modify something you have full control over.

The Result

With the extension loaded into Chrome as an active unpacked chrome://extensions it will switch out the CKEditor config to the one I control.

I can now jump natively into the HTML source that CKEditor is working from. The built in Chrome spell checked comes back to like, and I can edit things like blockquotes into the source.

The Future

I mostly post to the various StackExchange sites where they have a well tuned markdown editor. There is a Markdown plugin for CKEditor, it would be great to integrate this.

The Chrome extension is probably a bit heavy handed, it should be possible to run Javascript directly in the page once the CKEditor is loaded to reconfigure it on the fly. Maybe with something as simple as a bookmarklet. See Change CKEditor toolbar dynamically

There are indications that you can reenable the native browser spell checker using config.disableNativeSpellChecker = false;. I couldn't get this to work.

See Also:


Wednesday, October 21, 2015

Salesforce Debug logs with the Winter '16 Developer Console

I'm in the process of updating the FuseIT SFDC Explorer to use the Winter '16 (v35.0) APIs. One of the first things I've encountered are a number of changes with how debug logging works. These are somewhat documented in the Release Notes on Debugging.

In terms of actually integrating with Salesforce at the API level, TraceFlag no longer has a scopeId field to optionally represent the user being debugged. Instead it has a logType and DebugLevelId field. It's the latter that is particularly interesting.

Say you have two developers working in an Org. Ideally they would both be developing in separate orgs and then migrating changes into a common org, so let's say they are testing the integration of their changes.

  • Developer A starts the Developer Console.
  • A DebugLevel is created for Dev A with the DeveloperName 'SFDC_DevConsole'. Lets call it 7dl70000000000A, or DLA for short.
  • A TraceFlag is created for Dev A that references DLA. The TracedEntityId is the Developer A's User Id.
  • Developer B starts the Developer Console in the same org in their own session
  • You can't have two DebugLevel records with the same DeveloperName, so the Developer Console uses the existing DLA record. (See where I'm going with this?)
  • A TraceFlag is created for Dev B that references DLA. The TracedEntityId is Developer B's User Id.

So, what have we got? Both developers will see their logs in the Developer Console. However, they are sharing the same DebugLevel definition by default!

If either developer makes changes to the common SFDC_DevConsole debug levels it will affect the other developers logging for any future logs. If one developer deletes/removes the common DebugLevel it will cascade delete any associated TraceFlags. Hours of fun!

What can you do about this? You can add new DebugLevel records via the developer console. The one you have selected when you press Done will be sent back to the TraceFlag.

Thursday, October 8, 2015

Salesforce code quality test cases for Apex via static code analysis

Lets assume for a moment that your code coverage isn't a perfect 100% and there are some areas of your Apex that aren't exercised as part of the automated deployment test cases. If your suspension of disbelief goes that far, lets also assume that sometimes you add temporary code to aid in debugging those murky places. Something like:

System.assert(false, 'TEMP this assertion was only for debugging and will stop the execution so you can check the debug log etc...');

The general idea is that you can use this assertion to halt execution during manual testing. At which point it would be easy to inspect the debug log state or write out some useful state information in the assertion message. It's certainly not the most advanced debugging technique, but it is effective.

The main problem with the approach is what happens if you forget to take out that line. And then package the code base for deployment. Don't do that. It's not a good look.

Remember, the line in question may or may not be covered by the existing test cases. If it isn't it will sail right into the production managed package and then spring out and surprise a user at the most inopportune moment.

What if you could stop the package from proceeding based on the content of the Apex classes?

We need the poor man's static code analysis tool! And we need it to run as part of the packaging process.

Luckily, we already have Apex test cases that run as part of the packaging process. And we have the ApexClass sObject exposed to Apex which includes the Body of the classes within the current namespace. We just need some apex code that will scan through the Body of the apex classes and assert that they don't contain the problem string. Then add said test class to the package components. Something like:

@isTest
private class SampleCodeQualityTests {
 
 @isTest static void ensureThereAreNoTempAssertions() {
  string searchFor = '\'TEMP';

                // Exclude this class from the check. May also need to exclude based on namespace
  for(ApexClass ac : [Select Id, Name, Body From ApexClass where Name != 'SampleCodeQualityTests']) {
   integer index = ac.Body.indexOf(searchFor);
   if(index == -1) { continue; }

   integer lineNumber = lineNumberForIndex(ac.Body, index);
   string problemLine = extractLineByIndex(ac.body, index);
   System.assertEquals(-1, index, 'Apex class ' + ac.Name + '(' + ac.Id + ') contains the text ' + searchFor + ' at index ' + index + ' lineNumber:' + lineNumber + 
    '\n' + problemLine);
   
   //System.assert(!ac.Body.contains(searchFor), 'Apex class ' + ac.Name + '(' + ac.Id + ') contains the text ' + searchFor);
  }
 }

 private static integer lineNumberForIndex(string body, integer targetIndex) {
  integer lineNumber = body.substring(0, targetIndex).countMatches('\n') + 1;
  return lineNumber;
 }

 private static string extractLineByIndex(string body, integer targetIndex) {
  integer startOfLine = body.lastIndexOf('\n', targetIndex);
  integer endOfLine = body.indexOf('\n', targetIndex);

  string lineOfInterest = body.substring(startOfLine + 1, endOfLine);
  return lineOfInterest;
 }
 
}

Think of this more as a proof of concept. There are some obvious problems with it. For example, it isn't really parsing the Apex body. Just doing a dumb string search for a string that starts with TEMP. No consideration is being made for the context. It might be in comment. There are likely other issues around new line detection as well.

Still, it demonstrates the idea. Using Apex test cases to do static code analysis as part of the packaging and deployment steps.

Now if we could get to the SymbolTable in a testing context (i.e. no callout) there could be all sorts of interesting checks1:

  • Does each test method make at least one assertion?
  • Does each assertion include a message to the user?
  • Excessive lines of code in one Apex Class
  • Using System.debug statements - too many of these can be detrimental to performance.
  • Empty exception handling catch blocks
  • Unused local variables
  • Methods that aren't referenced by other code

Maybe having meaningless whitespace at the end of a code line or using somestring != null && somestring != '' rather than String.isEmpty(somestring) shouldn't be grounds to block packaging. You'll need to decide where the line gets drawn.

The other part of this type of testing is that it could form the basis of an automated org health check. Is the coverage percentage too low on a large class? Test fails. Trigger not bulkified? Test fails. An Apex class with 4000 lines of code that is padding for test coverage? Test fails!

You could easily drop them into an existing org to get a feel for the state of things before embarking on an modifications.

See also:


1. may or may not be actually possible with the SymbolTable alone.

Monday, September 28, 2015

Microsoft Ignite 2015 Round Up / Summary

I've summarised some of the most interesting/important parts of my TechEd 2015 NZ notes in this post.

The top five sessions for the conference based on session feedback:

  1. The Secret to Telling Awesome Stories from Microsoft's Chief Storyteller
  2. The Microsoft DevOps Vision
  3. Aligning Architecture to Organisation
  4. Public Speaking Skills for Quiet People
  5. Torment Your Colleagues with the 'Roslyn' .NET Compiler Platform

My top sessions, in no particular order

Great Artists Steal: Build better software by applying patterns and ideas from different languages

Orion Edwards - @borland

github.com/borlande/Ignite2015

  • Ruby
  • C# Scoping lambda for locks and transactions, building up and then committing.
  • Consider the parameter naming. Try and form a phrase. C# named parameters.
  • Swift
  • if let - unwarps null references in if/else.
  • C# - Create Optional generic type. Implicitly converts from null. Unwrap to take lamba to handle existing and null cases. Not great for input parameters.
  • Also, HtmlEncoded, Atomic, Tainted, Either
  • C++ - Zero Cost Abstraction
  • Can be used with struct in C# as well.
  • System.DateTime is an example. Useful for multiple data types in combination. When unit of measure is important.
  • If you can get a Haskell program to compile, it is probably free of bugs.
  • Grand Central Dispatch - from Apple
  • Queues are lightweight threads. You "dispatch" lambda functions to them.
  • Like Dispatcher in C#
  • Serial Queue. Jobs won't run in parallel.
  • github.com/borland/SerialQueue
  • Go
  • goroutines and channels
  • Channel with send and receive.

Torment Your Colleagues with "Roslyn" .NET Compiler Platform

Ivan Towlson

"The Duke Nukem Forever of compilers"
  • C# and Visual Basic compilers
  • Syntax trees for code
  • Semantic model
  • Visual Studio editor integration.
  • Roslyn Overview on dotnet github.
  • Syntax Visualizer - browse the entire tree.

The Secret to Telling Awesome Stories from Microsoft's Chief Storyteller

The Internet of Hackable Things

Kirk Jackson @kirkj - Felix Shi @comradpara

Project Premonition: Mosquito seeking drones and Microsoft Azure

So You Want to Present at Ignite

Chris Jackson

  • 60 seconds to get their attention. Then got to land the point.
  • How to be effective as an influencer.
  • Preparing your submission - earning the right to be heard.
  • Build your reputation. Humans make the decision on who gets the cut. Who is the person, are they credible.
  • Be authenticate.
  • Volunteer to speak. Start a blog if writing is your thing.
  • Less likely to pick the total unknown.
  • Understand the goal of the track. What is the story they are trying to tell.
  • Sell yourself to the track lead.
  • Preparing for your talk.
  • Presentation design and presentation aids.
  • What do the track owners looks for.
  • What is on the screen should only be there to pull people back on track.
  • Keep the number of words on the slide to a minimum.
  • Max 5 words on 5 lines.
  • Make it real with demos.
  • You already know the material. You don't remember what it is like to not know.
  • The first 60 seconds. If it sounds awkward and unrehearsed with it's unrehearsed.
  • Practice the first 60 seconds the most.
  • What is shipping and what are the trends.
  • Inspire, don't teach. Take an action at the end of 60 minutes. One to three things tops.
  • Tell Stories. List of facts do little to inspire us.
  • How do I make this real.
  • Pillars and organization. How do you support the primary objective of the talk. Is it driving the outcome you want.
  • Stick your landing. Try to avoid ending on Q&A
  • Get speaker training. Someone who will tell you stuff you need to hear.

The Cinematic Cloud - Pixar Studios

Developing Cross Platform Mobile Apps with XAML and MVVM

See Also:

Thursday, September 24, 2015

Salesforce Winter `16 breaking SOAP Partner API changes

Just encountered this connecting to a Winter `16 Sandbox Org (v35.0) via the SOAP Partner API from Summer `15 (v34.0).

I've cross posted to the Salesforce StackExchange and raised support case 12544039

System.InvalidOperationException: There is an error in XML document (1, 2239646). ---> System.InvalidOperationException: Instance validation error: 'urn:SearchLayoutFieldsDisplayed' is not a valid value for soapType.
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read2427_soapType(String s)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read2428_Field(Boolean isNullable, Boolean checkType)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read2458_DescribeSObjectResult(Boolean isNullable, Boolean checkType)
   at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read2692_describeSObjectsResponse()
   at Microsoft.Xml.Serialization.GeneratedAssembly.ArrayOfObjectSerializer193.Deserialize(XmlSerializationReader reader)
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)
   --- End of inner exception stack trace ---
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle)
   at System.Web.Services.Protocols.SoapHttpClientProtocol.ReadResponse(SoapClientMessage message, WebResponse response, Stream responseStream, Boolean asyncCall)
   at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters)
   at FuseIT.G4S.SalesforceConnector.SalesforcePartner.SforceService.describeSObjects(String[] sObjectType)

I suspect the problem is the new soapType's that were added in API version 35.0 for Winter `16.

As they didn't exist in v34.0 of the API, if they come back in the SOAP response the .NET XML Serialization won't know how to handle them.

The following API call against v34.0 of the Partner API shows the problem.

Request

To: https://cs5.salesforce.com/services/Soap/u/34.0/00DO00000000001

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:partner.soap.sforce.com">
   <soapenv:Header>
      <urn:SessionHeader>
         <urn:sessionId>00DO00000000001!AQYAQKme4PP0v_FURhdtYVACcTF6vsvw_NOTmyREALsessionId_8irB66snPfNb9MLrSDB7dT95YvZA8hFVFD63cyt</urn:sessionId>
      </urn:SessionHeader>
   </soapenv:Header>
   <soapenv:Body>
      <urn:describeSObjects>
         <urn:sObjectType>SearchLayout</urn:sObjectType>
      </urn:describeSObjects>
   </soapenv:Body>
</soapenv:Envelope>

Response Snippet

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:partner.soap.sforce.com" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <soapenv:Header>
      <LimitInfoHeader>
         <limitInfo>
            <current>39</current>
            <limit>5000000</limit>
            <type>API REQUESTS</type>
         </limitInfo>
      </LimitInfoHeader>
   </soapenv:Header>
   <soapenv:Body>
      <describeSObjectsResponse>
         <result>
           
     <!--SNIP-->
   
            <fields>
               <autoNumber>false</autoNumber>
               <byteLength>0</byteLength>
               <calculated>false</calculated>
               <caseSensitive>false</caseSensitive>
               <createable>false</createable>
               <custom>false</custom>
               <defaultedOnCreate>false</defaultedOnCreate>
               <deprecatedAndHidden>false</deprecatedAndHidden>
               <digits>0</digits>
               <filterable>false</filterable>
               <groupable>false</groupable>
               <idLookup>false</idLookup>
               <label>Fields Displayed</label>
               <length>0</length>
               <name>FieldsDisplayed</name>
               <nameField>false</nameField>
               <namePointing>false</namePointing>
               <nillable>true</nillable>
               <permissionable>false</permissionable>
               <precision>0</precision>
               <queryByDistance>false</queryByDistance>
               <restrictedPicklist>false</restrictedPicklist>
               <scale>0</scale>
               <soapType>urn:SearchLayoutFieldsDisplayed</soapType>   
               <!-- ^^^ PROBLEM IS HERE for v34.0 - this isn't a valid soapType until v35.0 -->
               <sortable>false</sortable>
               <type>complexvalue</type>
               <unique>false</unique>
               <updateable>false</updateable>
            </fields>
   
            <!--SNIP-->
               
            <keyPrefix>4co</keyPrefix>
            <label>Search Layout</label>
            <labelPlural>Search Layouts</labelPlural>
            <layoutable>false</layoutable>
            <mergeable>false</mergeable>
            <name>SearchLayout</name>
            <queryable>true</queryable>
            <replicateable>false</replicateable>
            <retrieveable>false</retrieveable>
            <searchable>false</searchable>
            <undeletable>false</undeletable>
            <updateable>false</updateable>
            <urlDetail xsi:nil="true"/>
            <urlEdit xsi:nil="true"/>
            <urlNew xsi:nil="true"/>
         </result>
      </describeSObjectsResponse>
   </soapenv:Body>
</soapenv:Envelope>

Ideally Salesforce shouldn't be sending elements from the v35.0 back to the v34.0 API. It's likely that SearchLayoutField, SearchLayoutButtonsDisplayed, SearchLayoutButton and RecordTypesSupported would cause similar issues with existing integrations.

Bonus namespace alterations

Be aware that some elements have changed namespace between v34.0 and v35.0. E.g. In v34.0 the soapType enumerations location and address were in the urn namespace ("urn:partner.soap.sforce.com"). In v35.0 They are now in the tns namespace (xmlns:tns="urn:partner.soap.sforce.com"). Make sure the API URL you are calling matches up with the WSDL version the classes were generated from.

Tuesday, September 22, 2015

Dreamforce 2015 Round-up / Summary

This was my second year attending Salesforce's annual Dreamforce conference in San Francisco. My first was last year.

Again, I focused mostly on the developer side of things which are around the Moscone West building - Home of the Dev Zone

Dev Zone


Sessions

Using Oculus Rift and Virtual Reality to Visualize Data on Salesforce

Introducing the Welkin Suite IDE for Salesforce

A Salesforce IDE built upon Visual Studio Code.

Custom Folder Structure - The ability to create sub folders within the project to organize components looks to be really useful.

Plus support for code comments.

Apex Enterprise Patterns: Building Strong Foundations

Patterns for:

  • Separation of Concerns + Factory Pattern
  • Service Layer
  • Domain Layer
  • Selector Layer

Parker Harris's True to the Core: What's Next for Our Core Products

Get Ready for a New Kind of Customer Success with Marc Benioff & Special Guests

175,000+ conference attendees. 25,000 of which are developers.

Demo of Thunder, the IoT cloud integrated with the Azure Event Hub for Office 365

Build Reliable Asynchronous Code with Queueable Apex

Apex Interactive Debugger

Fireside Chat with Satya Nadella and Jessi Hempel

A nice perk of becoming a Salesforce MVP this year was reserved setting access to some of the keynotes. In my case I got front row seating to the Satya Nadella keynote. Right in line behind Tony Prophet, who some of you may remember from last years main keynote.

Salesforce Developer Keynote

Introduction To Apex Asynchronous Callout Framework, aka, Continuation

Apex Testing Tips and Tricks - Community Campfire

Generically Call External Classes from Managed Packages

Using Type.forName to get the Type for a classname configured in a custom setting that the managed package can instantiate using .newInstance().

Understanding the Salesforce Architecture: How We Do the Magic We Do

Meet the Developers

Marc Benioff & Parker Harris Q&A

Sessions to catch up on

Dreamboat

<table style="width:194px;"><tr><td align="center" style="height:194px;background:url(https://www.gstatic.com/pwa/s/v/lighthousefe_20150907.00_p0/transparent_album_background.gif) no-repeat left"><a href="https://picasaweb.google.com/107745668347424492499/DreamboatCelebrityInfinity?authuser=0&feat=embedwebsite"><img height="160" src="https://lh3.googleusercontent.com/-1SeOvtlOsDM/Vf9Yix49CfE/AAAAAAACOvA/oVKLqsvNuEg/s160-c-Ic42/DreamboatCelebrityInfinity.jpg" style="margin:1px 0 0 4px;" width="160"></a></td></tr><tr><td style="text-align:center;font-family:arial,sans-serif;font-size:11px"><a href="https://picasaweb.google.com/107745668347424492499/DreamboatCelebrityInfinity?authuser=0&feat=embedwebsite" style="color:#4D4D4D;font-weight:bold;text-decoration:none;">Dreamboat - Celebrity Infinity</a></td></tr></table>

I'd sum up the Dreamboat as:

"among the most impressive accommodation you'll hardly even see"

Dreamforce is a pretty full on conference. You can be booked up from breakfast to as late as you want with meetings, sessions, keynotes, and after hours functions. This usually means waking up sometime around 6 am, maybe grabbing a quick breakfast, and then disembarking to the first meeting of the day. Then staggering back in to get some sleep before doing it all again tomorrow (assuming you make it back before midnight).

As far as accommodation goes, I'd have to rate the Dreamboat as among the most impressive I've seen. With multiple swimming pools, a gym, theater, basket ball court, and casino (not operational) all on board, there was no shortage of things to do. If only you can find the time. I don't think I made it to the Constellation Lounge during daylight hours and was surprised to see what it looked like during daylight hours.

Plus the views. Open the curtains before leaving for the morning to catch the sun coming up behind the bay bridge. Then see the silhouette of the Golden Gate bridge as the sun sets. Then the city skyline in the evenings.

Salesforce StackExchange breakfast

It was great to catch up with fellow users of the site prior to the start of the conference. A good way to put faces to names before things get going at the conference.

Random learning's

  • It's hard to take a photo of yourself getting a ride on a Pedicab without getting an ass in the picture. Do have small notes for payment.
  • Don't tweet a picture of Yoshiki unless you want to draw the attention of his fans.
  • You're going to need another bag to bring everything back home.
  • Do stop by the Admin zone for the professional headshots. Try to do it early in the week before the lack of sleep starts to catch up with you.
  • Get your twitter handle and avatar on your badge. I spend all year interacting with people via twitter, then struggle to identify them in real life.
  • AT&T do a 1.5GB nano sim card with 1.5GB of data for $45 USD. Combined with the conference WiFi, it was easily enough to get through and stay connected.
  • You can't give session feedback on sessions held in the DevZone theaters as you can't scan into them like you can with the breakout rooms.
  • If you're a developer, ignore the advice to leave your laptop behind. How are you going to complete the Mini Hacks or try anything out as you go if you don't have your tools on hand?
    My Surface Pro 3 was really useful. I could knock out the mini hacks, take down more complicated notes, do on the spot demos, ... Only catch for me was needing to change my date time settings to PST to connect to the WiFi. Go figure.
  • If you can time it right, drop ship things to the FedEx office at 726 Market St. It is only a quick walk from the conference and you can get a "Hold at FedEx location" when shipping.
  • Got a spare 30 minutes. Make a quick excursion to the Expo in Moscone North.
  • Visit the construction site for the Salesforce tower.
  • To use the pre-purchased BART return ticket you need to print out the receipt to present at the airport information booth. They won't accept a digital version on a screen.

UCSF Benioff Hospital Tour

The hospital has autonomous TUG robots helping with transport tasks around the hospital. I'm surprised these weren't featured in the IoT side of the conference.


See also:

Tuesday, September 8, 2015

Dreamforce 2015 is coming. Are you prepared?

Something has always bothered me a bit about conferences. The reveal of large new roadmap features mid-conference. A day of sessions has already gone by, and now there is a keynote telling you about a great new feature that you want to learn more about before the end of the conference. What to do? Ditch all the other sessions that you have otherwise already carefully selected to start learning about a new feature from scratch?

The recent Salesforce global Lightning Experience event has bypassed that by moving the big announcement to a few weeks out before the conference starts. There is some lead time for delegates to adjust session schedules and hit the conference with some knowledge of what they are getting themselves into.

With Dreamforce 2015 just around the corner, it's a great time to get ahead and get yourself some of that preparation knowledge for the changes that are going to occur to the core user interface.

The Developer Trail - Lightning Experience brings together 5 modules that cover areas of interest to a developer.

With the Lightning experience being so new and not available in all orgs yet (not GA), the modules mostly use multi-choice questions in the evaluation rather than inspecting your connected developer edition org. You will however cover some important topics, such as:

You also get to learn the importance of having good admins and not letting the developers do the rollout to a large org. Confetti canons for Everyone!

Once the developer trail is completed, some of the modules have cross over with other trials. Such as the Lightning Experience Basics, which is the starting point for some of the trails. So you would already have a good start on Admin Trail - Migrating to Lightning Experience.

See Also:

Friday, August 28, 2015

Dreamforce 2015 Session picks

Here are some of my current picks for Dreamforce 2015 sessions. They are mostly development focused. There could probably be more Lightning Experience sessions in the mix.

Likely I've made the same mistake as last year and overbooked myself with sessions, of which the list below is only a sampling. There will no doubt be lots of last minute chopping and changing. I'll probably triage based on if I can later catch the sessions recorded content. As Peter Knolle suggests, favoring sessions that occur in Moscone West will keep you mostly around the Developer Zone for technical sessions and save on trying to run from location to location.

Tuesday, September 15

Wednesday, September 16

Thursday, September 17

Friday, September 18

  • 12:30 PM Meet the Developers
    Because: Questions!
    As I've mentioned before, this is a great session to ask any burning questions of Salesforce developers.
  • 2:00 - 3:00 Marc Benioff & Parker Harris Q&A
    Because: That's a wrap
    Depending on the timing for getting to the airport for my flight home, it could be a fun way to end the conference.

Thursday, August 13, 2015

Salesforce Apex gotchas for a C# developer

There are a number of areas in Salesforce Apex that can routinely catch a developer coming from a .NET language out. Worse still, when constantly switching between Apex and C# the similarities in syntax can lead to confusion about what is applicable in the current language.

This post will be a continual work in progress. I'll come back to it as things come up.


Feature .NET C# Salesforce Apex
String Delimiter " - double quote ' - single quote
String Comparrison Case sensitive by default. String.Equals can take a StringComparison.OrdinalIgnoreCase InvariantCultureIgnoreCase or to ignore case Using == will be case insensitive. Use the String.equals or String.equalsIgnoreCase methods.
For each loop syntax foreach(X x in y) { } for(X x : y) { }
Case sensitivity Case sensitive Case insensitive. The variable map can easily conflict with the system type Map.
Collections List<string> or string[] are interchangeable in Apex.
Appending to the end of a list The List<T>.add(index, T) method doesn't work if the list is empty. See Should List<Object>.add(index, listElement) work if the index is at the upper bound?
params Apex doesn't have the concept of params like C# does. So if you try and pass a single string to something like String.format() you get the error
Variable does not exist: String
. Instead you need to explicitly put the formattingArguments into a List<string> or string[]

Source: Stuck on a “Variable does not exist: String” error

Properties Either automatic properties or explicit members required. Apex properties have an implicit member with the same name as the property.
Properties provide storage for values directly. You do not need to create supporting members for storing values.
Read-only and write-only automatic properties
Integers int or System.Int32 integer
Booleans bool or Boolean Boolean
Nullable Types Need to explicitly use Nullable<T> or ? notation to make primitive types nullable. All variables are implicitly nullable. In addition to the usual suspects like Integer, Boolean, Decimal, Double, Long, String, Time, Date, DateTime, it also includes Boolean. So you may be passed a Boolean parameter that is neither true or false.
Collections Dictionary Map
Exceptions Classes extending Exception must have a name ending in 'Exception'
Use this.setMessage(message); to set message rather that constructor.
switch statement switch Not available, use multiple if/else if/else statements. Add "Switch" or "Case" Statement to Apex
Generics Yes Internal Types only
Winter 13 Release Notes
Parameterized Interfaces No Longer Supported
Parameterized interfaces are not supported in Apex saved using Salesforce.com API version 26.0 and later.
Access Modifiers public "Public access is the most permissive access level. There are no restrictions on accessing public members" global "This means the method or variable can be used by any Apex code that has access to the class, not just the Apex code in the same application."
public - "This means the method or variable can be used by any Apex in this application or namespace."
In Apex, the public access modifier is not the same as it is in Java. This was done to discourage joining applications, to keep the code for each application separate. In Apex, if you want to make something public like it is in Java, you need to use the global access modifier.

See also:

Friday, August 7, 2015

Salesforce UNKNOWN_EXCEPTION, portal account owner must have a role: []

After being dropped into an existing Salesforce Org to make changes the first thing I like to do is run the existing test cases. It gives me a rough lay of the land and a feel for the health of Org.

Is it just scrapping by the 75% threshold? Or, worse still, are current test cases already failing in production?
These are things that are going to make change set deployments from a sandbox difficult. More so when combined with any further changes I want to make.

One such recent Org started with a large number of test cases failing with the message:

UNKNOWN_EXCEPTION, portal account owner must have a role: []

This was occurring when the test methods were attempting to insert a User with the "Customer Community Login User" profile. Oddly this wasn't occurring in the sandbox.

The test cases in question were inserting an Account, Contact, and User record that were all related to each other. From the error message, the Salesforce User that inserts that Account needs to have a role assigned. For some reason the Sandbox user I was provided had the Role set, and the production user didn't.

Setting my current users Role in production fixed that particular issue.

For good measure, I updated the test cases with the following to make the resolution more apparent.

// UNKNOWN_EXCEPTION, portal account owner must have a role: []
System.assertNotEquals(null, UserInfo.getUserRoleId(), 'The current user with Id: ' + UserInfo.getUserId() + 
             ' Name: ' + UserInfo.getUserName() + ' needs a Role assigned to test Portal/Community users');

Onward to fix the the remaining existing failures...