Create a gist now

Instantly share code, notes, and snippets.

Prototype Invocable Method wrapping the Salesforce Metadata API to allow management of Picklist Items via Process Builder and Visual Flow
/**
* Copyright (c) 2015, FinancialForce.com, inc
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the FinancialForce.com, inc nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**/
/**
* This an early proof of concept for calling the Apex Metadata API from
* Process Builder or indeed Visual Flow via Invocable Methods
**/
public with sharing class AddPicklistItemAction {
public class Request {
@InvocableVariable(label='Picklist Item' required=true)
public String pickListItem;
@InvocableVariable(label='Fully Qualified Field Name' required=true)
public String customFieldName;
}
@InvocableMethod(
label='Adds picklist list items to given custom fields'
description='Will submit a request to add the given picklist items to the given custom fields')
public static void addPickListItem(List<Request> requests) {
// Must enqueue the work as Apex Metadata API calls our HTTP callouts
System.enqueueJob(new DoWork(UserInfo.getSessionId(), requests));
}
/**
* Class retrieves the custom field definitions and adds the given picklist items
*
* TODO: Time did not permit the following in time for SF1 London and are still outstanding
* - Error reporting, email the user, or chatter post on the record?
**/
public class DoWork implements System.Queueable, Database.AllowsCallouts {
private final String sessionId;
private final List<Request> requests;
public DoWork(String sessionId, List<Request> requests) {
this.sessionId = sessionId;
this.requests = requests;
}
public void execute(System.QueueableContext ctx) {
// Metadata Service
MetadataService.MetadataPort service = new MetadataService.MetadataPort();
service.endpoint_x = service.endpoint_x.replace('http:', 'https:'); // Workaround to Apex MD API bug in Batch
service.SessionHeader = new MetadataService.SessionHeader_element();
service.SessionHeader.sessionId = sessionId;
// Custom Fields
Map<String, List<String>> newPickListItemsByCustomField = new Map<String, List<String>>();
for(Request request : requests) {
List<String> pickListItems = newPickListItemsByCustomField.get(request.customFieldName);
if(pickListItems==null)
newPickListItemsByCustomField.put(request.customFieldName, pickListItems = new List<String>());
pickListItems.add(request.pickListItem);
}
// Read Custom Fields
List<MetadataService.CustomField> customFields =
(List<MetadataService.CustomField>) service.readMetadata('CustomField',
new List<String>(newPickListItemsByCustomField.keySet())).getRecords();
// Add pick list values
for(MetadataService.CustomField customField : customFields) {
List<String> pickListItems = newPickListItemsByCustomField.get(customField.fullName);
for(String pickListItem : pickListItems) {
metadataservice.PicklistValue newPicklist = new metadataservice.PicklistValue();
newPicklist.fullName = pickListItem;
newPicklist.default_x=false;
customField.picklist.picklistValues.add(newPicklist);
}
}
// Update Custom Fields and process an failed saves
List<String> errors = new List<String>();
for(MetadataService.SaveResult saveResult : service.updateMetadata(customFields)) {
try {
handleSaveResults(saveResult);
} catch (Exception e) {
errors.add(saveResult.fullName + ' : ' + e.getMessage());
}
}
if(errors.size()>0)
throw new AddPicklistItemActionException(String.join(errors, '/n'));
}
/**
* Example helper method to interpret a SaveResult, throws an exception if errors are found
**/
public void handleSaveResults(MetadataService.SaveResult saveResult)
{
// Nothing to see?
if(saveResult==null || saveResult.success)
return;
// Construct error message and throw an exception
if(saveResult.errors!=null)
{
List<String> messages = new List<String>();
messages.add(
(saveResult.errors.size()==1 ? 'Error ' : 'Errors ') +
'occured processing component ' + saveResult.fullName + '.');
for(MetadataService.Error error : saveResult.errors)
messages.add(
error.message + ' (' + error.statusCode + ').' +
( error.fields!=null && error.fields.size()>0 ?
' Fields ' + String.join(error.fields, ',') + '.' : '' ) );
if(messages.size()>0)
throw new AddPicklistItemActionException(String.join(messages, ' '));
}
if(!saveResult.success)
throw new AddPicklistItemActionException('Request failed with no specified error.');
}
}
public class AddPicklistItemActionException extends Exception {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment