Managed package upgrade fail

Software development is always an iterative process and repeatability is key. So multiple deployments to internal or external test teams or to production environments should produce predictable results.

The good news is that a managed package can achieve this for most (but unfortunately not all) parts of a Force.com app if fresh installs are done. But in reality managed package upgrades are far more common than fresh installs because they can be done without disturbing existing data: test teams have test data setup and want to log bugs that link to examples of problems; production environments contain (large volumes of) production data. The bad news is that by design the managed package upgrade process chooses to not update layouts and picklist entries. So as an app evolves, layout changes such as new fields or re-arrangements of existing fields or new custom buttons and links are lost by default. And so are new picklist entries meaning functionality based on the new values is also lost by default.

I understand the reason for this approach is that layouts and picklist entries can be edited after they are installed and so an upgrade process that updates will overwrite any local changes and I agree that that would be a bad thing. But based on my experience over the last year with multiple customers, issuing lists of manual update steps and dealing with bugs about fields missing in layouts and values missing in picklists is also a bad thing. And with customers wanting to operate in multiple orgs – development, QA, UAT, production – and work iteratively with updates every one or two weeks the cost (in dollars, in risk to your reputation, and emotional drudgery) of trying to manually keep consistency is large.

I suggest that there are some apps where end-user customization is normal and expected and there are other apps where no customization or more controlled customization (including version control) is normal. For the latter type of app, having a managed package upgrade option to update layout and picklist entries seems like an important and obvious feature.

My attempts to influence salesforce on this directly have failed as have several postings on the ideas forum. If you are a managed package developer or are thinking of becoming one, I urge you to raise this issue with salesforce. Or be prepared to suffer poor quality and high costs for the foreseeable future.

Creating and running a fake web service from a WSDL

I needed to develop some Apex web service callout code – never a pleasant experience see e.g. When “Generate from WSDL” fails – hand-coding web service calls – but did not have access to a running instance of the service. Java 6 includes web service technology by default and I was pleasantly surprised how easy it is to create and run a fake web service to develop against using this…

Having run wsimport to generate the web service classes from the WSDL, creating a server implementation class is simply a matter of extending the service interface and using Eclipse’s “Add unimplemented methods” to create the server skeleton then adding a couple of annotations:

package com.claimvantage.test;

import javax.jws.WebService;
import javax.xml.ws.BindingType;

// Other imports removed

@WebService(
        serviceName = "FNCEWS40Service",
        portName = "FNCEWS40MTOMPort",
        targetNamespace = "http://www.filenet.com/ns/fnce/2006/11/ws/MTOM/wsdl",
        wsdlLocation = "com/claimvantage/test/filenet.wsdl",
        endpointInterface = "com.claimvantage.filenet.FNCEWS40PortType"
        )
@BindingType(value="http://java.sun.com/xml/ns/jaxws/2003/05/soap/bindings/HTTP/")
public class ServiceFakeImpl implements FNCEWS40PortType {

    @Override
    public ObjectSetType executeSearch(SearchRequestType request, Localization header) throws FaultResponse {
        // TODO fake implementation goes here
        return null;
    }

    // Other methods removed
}

The Endpoint class makes running this service very easy (with no separate web server or XML configuration required):

package com.claimvantage.test;

import javax.xml.ws.Endpoint;

public class ServiceRunner {

    private static final String URL = "http://localhost:9080/wsi/FNCEWS40MTOM/";

    public static void main(String[] args) {
        Endpoint endpoint = Endpoint.create(new ServiceFakeImpl());
        endpoint.publish(URL);
        System.out.println("Service running at " + URL);
    }
}

Then make the fake service accessible from the internet (and so from Force.com) by e.g. setting up firewall port forwarding.

Debug output and fake responses can be added to the fake server methods. When combined with debug logging in the Apex and a tool such as tcpmon to view the request and response headers and XML this gives full visibility to what is going on across the whole request/response cycle.

“Combobox” input in Visualforce

To quote the Force.com documentation:

A combobox is a picklist that also allows users to type a value that is not already specified in the list.

A picklist field itself stores a string value and does not enforce that the value stored is one of the picklist entry values. The “combobox” presentation just makes this flexibility visible in the UI. Here is an example in the Task editing page:

For some reason this presentation type hasn’t been included in the field types that can be specified for custom fields so it isn’t possible to get this sort of editing capability via default (layout-based) UI. But it can be created using Visualforce, and here is one way to do that.

My requirement was a bit more complicated than a basic combobox in that the list of values to be offered also depended on another field on the page. Also to save development work and to make the interface a little cleaner I didn’t use a pop-up window. Here is an example of the resulting page:

When the “Type” field is changed alternate candidate “Document Name” links are presented and when a link is clicked the text is transferred to the “Document Name” field. The square brackets around each candidate is copied from how the default date is presented next to a date field. The user can of course enter any other “Document Name” value they like by just typing into the field directly.

Here is the Visualforce with the Javascript that uses JSON data provided by the controller to determine what links to write into the HTML to the right of the “Document Name” field:

<apex:page id="p" standardController="cve__Document__c" extensions="DocumentController">
    <apex:sectionHeader title="Document Edit" subtitle="New Document" />
    <apex:pageMessages />
    <apex:form id="f">
        <apex:pageBlock id="pb" title="Document Edit" mode="edit">
            <apex:pageBlockButtons >
                <apex:commandButton value="Save" action="{!save}"/>
                <apex:commandButton value="Cancel" action="{!cancel}"/>
            </apex:pageBlockButtons>
            <apex:pageBlockSection id="pbs" title="Information" columns="1">
                <apex:inputField value="{!cve__Document__c.cve__Claim__c}"/>
                <apex:inputField id="type" value="{!cve__Document__c.cve__Type__c}" onchange="cvOfferOptions();"/>
                <apex:pageBlockSectionItem id="pbsi">
                    <apex:outputLabel value="{!$ObjectType.cve__Document__c.fields.Name.label}"/>
                    <apex:panelGrid columns="2">
                        <apex:inputField id="name" value="{!cve__Document__c.Name}" style="width: 20em" required="true"/>
                        <apex:outputPanel id="panel">
                        </apex:outputPanel>
                    </apex:panelGrid>
                </apex:pageBlockSectionItem>
                <apex:inputField value="{!cve__Document__c.cve__Required__c}"/>
                <apex:inputField value="{!cve__Document__c.cve__Received__c}"/>
            </apex:pageBlockSection>
        </apex:pageBlock>
    </apex:form>
<script type="text/javascript">
function cvDocuments() {
    var documentNames = {!documentNamesJson};
    var type = document.getElementById('p:f:pb:pbs:type');
    var selectedType = type.options[type.selectedIndex].text;
    return documentNames[selectedType];
}
function cvOfferOptions() {
    var documents = cvDocuments();
    var html = '';
    if (documents != null) {
        for (var i = 0; i < documents.length; i++) {
            html += ''
                    + '<div>[ <a href="javascript:cvPickValue('
                    + i
                    + ');" title="Click to set as \'Document Name\'">'
                    + documents[i]
                    + '</a> ]</div>'
                    ;
        }
    }
    var panel = document.getElementById('p:f:pb:pbs:pbsi:panel');
    panel.innerHTML = html;
}
function cvPickValue(index) {
    var name = document.getElementById('p:f:pb:pbs:pbsi:name');
    name.value = cvDocuments()[index];
}
function cvMakeDoubleDelegate(function1, function2) {
    return function() {
        if (function1) {
            function1();
        }
        if (function2) {
            function2();
        }
    }
}
window.onload = cvMakeDoubleDelegate(window.onload, cvOfferOptions);
</script>
</apex:page>

The controller extension is just responsible for providing the JSON string, with the data access and “save” and “cancel” actions left to the standard controller:

public with sharing class DocumentController {

    private ApexPages.StandardController standardController;

    public DocumentController(ApexPages.StandardController standardController) {
        this.standardController = standardController;
    }

    // This controller extension just supplies this data with all other functionality using the standard controller
    public String documentNamesJson {
        get {
            Map<String, List<String>> m = new Map<String, List<String>>();
            m.put('Legal', new String[] {
                    'Appeal',
                    'Complaints',
                    'Other'
                    });
            m.put('Medical', new String[] {
                    'Attending Physician\'s Statement',
                    'Medical Records',
                    'Medical Reviews',
                    'Pharmacy',
                    'Toxicology',
                    'Autopsy/Coroner\'s Report',
                    'Other'
                    });
            m.put('Financial', new String[] {
                    'Income Records',
                    'Interest Calculation',
                    'Collection Referral',
                    'Refund',
                    'Other'
                    });
            return json(m);
        }
    }
    
    private String json(Object o) {
        JSONGenerator generator = JSON.createGenerator(false);
        generator.writeObject(o);
        return generator.getAsString();
    }
}

Note that while in this example the candidate values are expressed in Apex code, DescribeFieldResult.getPicklistValues can be used to obtain the entry values defined for a picklist field.

Keeping track of the selected tab of an apex:tabPanel in the controller – using Javascript remoting

PS See BarryC’s comments below about only invoking the remoting when the tab label is clicked.

While this Keeping track of the selected tab of an apex:tabPanel in the controller implementation works, taking a look at the “Network” view of Chrome’s “Developer Tools” brings the bad news that the call to the server made when a tab is clicked on involves (in my case) a 700k byte data transfer and an elapsed time of around 3 seconds. This is a lot of overhead to just transmit the name of the selected tab to the server. This cost and time delay is not immediately apparent because the browser remains responsive. Looking at the payload sent to the server, the problem is that the full view state is being sent as part of the request.

The “writing the name of that tab back to the controller” technique I should have used in the first place is JavaScript remoting. Changing to that reduces the data transfer size to a few hundred bytes and the elapsed time to a few hundred milliseconds.

Here is the changed Visualforce and Apex:

<apex:page id="p" standardController="Xyz__c" extensions="XyzController">
    <apex:form id="f">
        <apex:tabPanel id="tp" switchType="client" value="{!selectedTab}" onclick="setSelectedTabOnController()">
            <apex:tab name="Tab1" label="Tab1" id="Tab1">
                <!-- Tab content -->
            </apex:tab>
            <apex:tab name="Tab2" label="Tab2" id="Tab2">
                <!-- Tab content -->
            </apex:tab>
        </apex:tabPanel>
    </apex:form>
<script type="text/javascript">
function getSelectedTabName() {
    if (RichFaces) {
        var tabs = RichFaces.panelTabs['p:f:tp'];
        for (var i = 0; i < tabs.length; i++) {
            var tab = tabs[i];
            if (RichFaces.isTabActive(tab.id + '_lbl')) {
                return tab.name;
            }
        }
    }
    return null;
}
function setSelectedTabOnController() {
    // Invoke server remote action method
    XyzController.updateLastViewedTab('{!Xyz__c.Id}', getSelectedTabName(), callbackHandler)
}
function callbackHandler(result, event) {
    if (event.type == 'exception') {
        alert(event.message);
    }
}
</script>
</apex:page>
global with sharing class XyzController  {
    private Xyz__c xyz;
    @RemoteAction
    global static void updateLastViewedTab(Id xyzId, String tabName) {
    	upsert new Xyz__c(Id = xyzId, LastViewedTab__c = tabName);
    }
    public String selectedTab {
        get {
            if (selectedTab == null) {
                if (xyz.LastViewedTab__c != null) {
                    selectedTab = xyz.LastViewedTab__c;
                } else {
                    selectedTab = 'Tab1';
                }
            }
            return selectedTab;
        }
        // Setter has to be public as some (but not all) actions do result in an update
        set;
    }
    // Other code...
}