Andy in the Cloud

From BBC Basic to Force.com and beyond…

49 thoughts on “Apex Metadata API and Spring’14 : Keys to the Kingdom!

  1. This is so good, thanks for sharing. Opens up so many possibilities for tools – I’m having a play with it this weekend…

  2. Hey Andrew
    I’m currently in need to create and read Sharing Rules through API. I have seen that you put them here as unsupported, can you spare some details why? I’m curious what I will have to do to overcome this.

    Thanks

    • It’s due to them using an inheritance model in the WSDL, that needs to be manually applied in the generated code or ideally through the patcher. The fields in the base types need to be lifted to the top level types since the XML marshaller doesn’t look in base types when serializing and deserializing. Should be able to get it to work, actually looking at this problem for Workflow Actions now actually…. will keep you posted…

      • Ok so Workflow actions are done, but are way easier, since the base type didn’t actually have anything in them. I’ve reviewed the Sharing Rules metadata base types (see the WSDL) and they do. So will take either some manual effort or prefferably update the patch to modify the code (quicker for upgrades in the future). I’m thinking it maintains the inheritance of the types as per the XML Schema but the base types become empty with the top level types having all the fields, this is the only way to get it to work i think. Unfortunatly i don’t have a great deal of time this weekend, but may be able to get to this next week sometime. If you want to have a go at this yourself, i’d be happy to help co-ordainte, if your up for that raise a GitHub issue and we can talk more easily there. Thanks! 🙂

      • Just to let you know. I was trying to make it working but was not able, so I decided to contact support. After 6 weeks of R&D escalation they confirmed that it’s currently impossible to create sharing rule through createMetadata(). Here is the link to known issue: https://success.salesforce.com/issues_view?id=a1p30000000T4Q3AAK

      • thanks for letting me know about this bug, once it’s fixed I will put more effort into adapting the apex Metadada API wrapper

      • Hey Andrew

        Is the sharing rule still unsupported?

        Thanks.

      • Currently no, sorry. Needs an enhancement to the patcher class i use to modify the generated Apex. Please raise this as an enhancement on the GitHub issues site though.

      • Sorry could not get back to you earlier. Thanks for raising up as a github issue.

  3. Hi, MetadataService.readMetadata() does not work for CustomObject itself. Below code throws exception saying “Missing dependent object: Class: MetadataService.readCustomObjectResponse_element”.
    service.readMetadata(‘CustomObject’, new String[] {‘Job_Application__c’}).getRecords();

    Looking the debug print, the SOAP response is returned successfully, so it seems an issue is in client-side conversion. Any idea how to fix this issue?

  4. Hi Andrew, thanks so much for getting this update done, it’s awesome. I thought I might leave you with a Gist I put together that queries the Tooling API for all Workflow names, constructs the Tablename.Workflowname syntax and passes to the readMetadata service. This is written in a batchable class due to the 10 callout limit per request. I’m in the process of working through saving the metadata information and then searching. Ultimately we are finding it extremely difficult to complete role and profile reviews because if we change a profile or role name, we impact things like validation rules and workflow rules. Anyways, feel free to critique and add suggestions if you like!


    global class WorkflowRuleBatchable implements Database.Batchable<String>, Database.AllowsCallouts, Database.Stateful
    {
    global final List<MetadataService.WorkflowRule> wfrs;
    private final String sessionId;
    global WorkflowRuleBatchable(String sessionId)
    {
    this.sessionId = sessionId;
    wfrs = new List<MetadataService.WorkflowRule>();
    }
    global List<String> start(Database.BatchableContext bc)
    {
    String salesforceHost = System.Url.getSalesforceBaseURL().toExternalForm();
    String urlHost = salesforceHost + '/services/data/v30.0/tooling/query/?q=Select+Id+,Name,+TableEnumOrId+from+WorkflowRule';
    HTTPResponse res = metadataQuery(urlHost);
    //system.debug('workflow res = ' + res);
    //system.debug('workflow res.getBody() = ' + res.getBody());
    List<String> workflowRuleNames = new List<String>();
    WorkflowRule wfrs = (WorkflowRule) System.JSON.deserialize(res.getBody(), WorkflowRule.class);
    for(Integer j=0; j < wfrs.records.size(); j++ )
    {
    String fullName = wfrs.records[j].TableEnumOrId + '.' + wfrs.records[j].Name;
    workflowRuleNames.add(fullName);
    }
    return workflowRuleNames;
    }
    global void execute(Database.BatchableContext bc, List<String> scope)
    {
    MetadataService.MetadataPort service = new MetadataService.MetadataPort();
    service.SessionHeader = new MetadataService.SessionHeader_element();
    service.SessionHeader.sessionId = this.sessionId;
    // Read Workflow Alert
    MetadataService.WorkflowRule wfr = (MetadataService.WorkflowRule) service.readMetadata('WorkflowRule', scope).getRecords()[0];
    wfrs.add(wfr);
    }
    global void finish(Database.BatchableContext bc)
    {
    system.debug('wfrs = ' + wfrs);
    }
    HTTPResponse metadataQuery(String urlHostVariable)
    {
    HttpRequest req = new HttpRequest();
    req.setMethod('GET');
    req.setEndpoint(urlHostVariable);
    req.setHeader('Content-type', 'application/json');
    req.setHeader('Authorization', 'Bearer ' + this.sessionId);
    Http http = new Http();
    return http.send(req);
    }
    class WorkflowRule
    {
    public Integer size;
    public Integer totalSize;
    public Boolean done;
    public Object queryLocator;
    public String entityTypeName;
    public List<Records> records;
    public WorkflowRule(Integer size, Integer totalSize, Boolean done, Object queryLocator, String entityTypeName, List<Records> records)
    {
    this.size = size;
    this.totalSize = totalSize;
    this.done = done;
    this.queryLocator = queryLocator;
    this.entityTypeName = entityTypeName;
    this.records = records;
    }
    }
    class Attributes
    {
    public String type;
    public String url;
    }
    class Records
    {
    public Attributes attributes;
    public String Id;
    public String Name;
    public String TableEnumOrId;
    public Records(Attributes attributes, String Id, String Name, String TableEnumOrId)
    {
    this.attributes = attributes;
    this.Id = Id;
    This.Name = Name;
    this.TableEnumOrId = TableEnumOrId;
    }
    }
    }

    • Nice thanks for sharing! Will take a look! Did you use the Apex Tooling API by any chance? 🙂

      • global List start(Database.BatchableContext bc)
        {
        String salesforceHost = System.Url.getSalesforceBaseURL().toExternalForm();
        String urlHost = salesforceHost + ‘/services/data/v30.0/tooling/query/?q=Select+Id+,Name,+TableEnumOrId+from+WorkflowRule’;

        HTTPResponse res = metadataQuery(urlHost);

        //system.debug(‘workflow res = ‘ + res);
        //system.debug(‘workflow res.getBody() = ‘ + res.getBody());
        List workflowRuleNames = new List();

        WorkflowRule wfrs = (WorkflowRule) System.JSON.deserialize(res.getBody(), WorkflowRule.class);

        for(Integer j=0; j < wfrs.records.size(); j++ )
        {
        String fullName = wfrs.records[j].TableEnumOrId + '.' + wfrs.records[j].Name;
        workflowRuleNames.add(fullName);
        }
        return workflowRuleNames;
        }

        Absolutely 😀

      • Nice, check out the Apex Tooling API wrapper to avoid some of the Json plumbing you have going on here though.

      • I would but my understand was that the Tooling API Wrapper doesn’t currently support WorkflowRule and I was just looking to do a quick POC. Let me know if I’m wrong on this one.

      • It looks like they just added it! Which means we need to update the Apex Tooling API wrapper! 🙂 http://www.salesforce.com/us/developer/docs/api_tooling/Content/sforce_api_objects_workflowrule.htm Want to help?

      • Sure I’ll take a look at it 🙂

      • Nice! An easy way to generate the Apex Types is to run the WSDL to Apex generator then copy and paste the types (stripping out the XML marshalling metadata). Saves having to reverse engineer the fields and type structures from the JSON returned if you use the WSDL as a reference. Note that the generate types from the WSDL don’t include the inheritance for example extending WorkflowAction (as schema types do), but you can apply this manually also.

      • Update for Workflow Rule to the ToolingAPI has been submitted.

      • Wow that was fast! Will take a look! Thanks for the contribution!

  5. I’ve been searching for a way to get access to the properties of a report folder. Has anyone done this via the metadata API. I can get a list of the report folders and then get information about the individual reports but what I want is the information on the actual report folder itself so that I can see who the report is sharedTo. Any help would be greatly appreciated!

  6. Andrew, I am able to connect salesforce org, retrieve SOQL query result using C# coding (with visual studio). I am trying to figure out the way to add picklist value through C# coding. Is it possible using MetaData API?

  7. Hi Andrew Fawcett, Excellent work. I have one question. Can we update package version through metadata it?

  8. I need to do 2 things with apex
    1. Make a copy of a layout
    2. Grab a layout from an installed package and update another layout

    Are these possible? Great blog!

    • 1. Yes, this is possible, retrieve the layout, change the fullName and use createMetadata or upsertMetadata
      2. You can only get the initial installations of managed layouts, after that, package upgrades don’t update layouts. But yes, you can retrieve these and use the information to update another layout.

      P.S. Sorry i missed this comment until now! 😦

  9. Hey, I was trying to share a report using the Metadata,FolderShare class but I am geting an error.
    Can you help me with this?
    I saw a thread above with respect to this. Were you able to do this?
    I am trying it like this:
    MetadataService.ReportFolder rfa = (MetadataService.ReportFolder) service.readMetadata(‘ReportFolder’, new String[]{‘test’}).getRecords()[0];

    MetadataService.FolderShare folderShareService = new MetadataService.FolderShare();
    folderShareService.accessLevel = ‘View’;
    folderShareService.sharedTo = ‘AllInternalUsers’;
    folderShareService.sharedToType = ‘Organization’;
    rfa.folderShares.add(folderShareService);
    service.updateMetadata( new MetadataService.Metadata[] { rfa });

  10. Hi Andy,

    Is it possible to create sharing rules using Metadata? If yes, can you add a sample to create or read a sharing rule in your example class https://github.com/financialforcedev/apex-mdapi/blob/master/apex-mdapi/src/classes/MetadataServiceExamples.cls

    I have been trying to do so, but always getting an error.

    If not in version 32.0, will it be supportable in version 33.0 as in version 33.0 salesforce is will change the components containing sharing rules.

    • It is currently not possible, i hope once the library is updated to 33 (Spring’15). I will wait for the service to be upgraded early Feb before doing this however.

  11. Nice work Andy. I have an issue when using the saveResult method. Here is the code
    List results = service.updateMetadata(
    new MetadataService.Metadata[] { customField });
    handleSaveResults(results[0]);

    and here is the error I’m getting “Method does not exist or incorrect signature: handleSaveResults(MetadataService.SaveResult)”

    Thank you for your help.

    • Does the method exist?

      • Here is the full code:

        public class MyTestingUpdatePicklist {
        public static void updatePicklistField()
        {
        MetadataService.MetadataPort service = MetadataServiceExamples.createService();
        MetadataService.CustomField customField = new MetadataService.CustomField();
        customField.fullName = ‘Opportunity.incumbent__c’;
        customField.label = ‘Incumbent’;
        customField.type_x = ‘Picklist’;
        metadataservice.Picklist pt = new metadataservice.Picklist();
        pt.sorted= false;
        metadataservice.PicklistValue eyecare = new metadataservice.PicklistValue();
        eyecare.fullName= ‘eyecare’;
        eyecare.default_x=false ;
        metadataservice.PicklistValue eyeone = new metadataservice.PicklistValue();
        eyeone.fullName= ‘eyeone’;
        eyeone.default_x=false ;
        pt.picklistValues = new list{eyecare,eyeone};
        customField.picklist = pt ;
        List results = service.updateMetadata(
        new MetadataService.Metadata[] { customField });
        handleSaveResults(results[0]);
        // handleSaveResults( service.updateMetadata(
        // new MetadataService.Metadata[] { customField })[0]);
        // SaveResult[] results = metadataConnection.updateMetadata(Metadata[] customField);
        }
        }

        Thank you so much Andy fro your help.

  12. Hi Andrew,

    Thanks for your kindly sharing.

    I have followed the steps to read a custom picklist field metadata,in which “Position__c” is a custom object and “Sub_Status__c” is a custom picklist field of position object:

    system.debug((MetadataService1.CustomField)stub.readMetadata(‘CustomField’,new String[]{‘Position__c.Sub_Status__c’}).getRecords()[0]);

    But got the exceptions as below:

    System.CalloutException: Web service callout failed: Unable to parse callout response. Apex type not found for element valueSet.

    It seems that there is a element called valueSet in callout response which Apex can not parse properly, and I don’t know how to solve this problem.

  13. Pingback: Add ‘Detail Page Button’ to Contact Layout using the MetaData API – GrindSkills

Leave a Reply to Andrew Fawcett Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s