Andy in the Cloud


Home | About | Archives


Scripting the Apex Metadata API and Batch Apex Support

May 6, 2013 11:20 am

The Apex Metadata API (a native Apex wrapper around the Salesforce equivalent) has had a steady increase of followers and questions since it was created in October 2012. Judging by the feedback I have had its enabling quite a few time saving and wizard style solutions in your native applications! This blog introduces a new example using Batch Apex to ‘script’ the creation of custom objects, fields, pages etc. in an org more easily in a none UI context.

The Salesforce Metadata API is an asynchronous API, which means you have to poll it to determine the fate of your requests. At launch I provided some examples of doing this via apex:actionPoller using Visualforce. Recently given some queries on the forums and this blog, I decided to invest a little more in a Batch Apex example. The new MetadataCreateJob class (and test) implements Batch Apex using a custom iterator to process the Metadata items in order and will even wait for proceeding items to complete, to handle dependencies.

The following example (included here in the repo) will perform the following steps with the given name.

  1. Create a custom object of the given name
  2. Create 2 custom fields on the object
  3. Create a Visualforce page that references the two fields using the standard controller
  4. Email the user once the above is completed, including any errors returned by checkStatus.

Trying out the sample code…

If you want to try out the following example install, MetadataService.cls, MetadataServiceExamples.cls and MetadataCreateJob.cls in your org (test classes are also available). You will also need to add to your Remote Site Settings (under Security Controls), https://na11.salesforce.com (changing na11 to your org instance). Then issue the following command from the Developer Console or Execute Annoynmous from Eclipse.

MetadataServiceExamples.dynamicCreation('Test');

Once you receive the confirmation email, you will then see the following items in your org…

Screen Shot 2013-05-06 at 11.48.55
Screen Shot 2013-05-06 at 11.49.05
Screen Shot 2013-05-06 at 11.49.22

If you repeat the above you should see the following errors (returned from Metadata API’s checkStatus operation) in the email once the job is complete.

Screen Shot 2013-05-06 at 11.41.24

The code to perform the above steps is shown below, note how the ‘wait’ parameter is used on the job items to cause the items to be processed only once a dependent proceeding item has been completed.

	public static void dynamicCreation(String objectName)
	{
		// Define Metadata item to create a Custom Object
		MetadataService.CustomObject customObject = new MetadataService.CustomObject();
		customObject.fullName = objectName + '__c';
		customObject.label = objectName;
		customObject.pluralLabel = objectName+'s';
		customObject.nameField = new MetadataService.CustomField();
		customObject.nameField.type_x = 'Text';
		customObject.nameField.label = 'Test Record';
		customObject.deploymentStatus = 'Deployed';
		customObject.sharingModel = 'ReadWrite';

		// Define Metadata item to create a Custom Field on the above object
		MetadataService.CustomField customField1 = new MetadataService.CustomField();
		customField1.fullName = objectName+'__c.TestField1__c';
		customField1.label = 'Test Field 1';
		customField1.type_x = 'Text';
		customField1.length = 42;

		// Define Metadata item to create a Custom Field on the above object
		MetadataService.CustomField customField2 = new MetadataService.CustomField();
		customField2.fullName = objectName+'__c.TestField2__c';
		customField2.label = 'Test Field 2';
		customField2.type_x = 'Text';
		customField2.length = 42;

		// Define Metadata item to create a Visualforce page to display the above field
		MetadataService.ApexPage apexPage = new MetadataService.ApexPage();
		apexPage.apiVersion = 25;
		apexPage.fullName = objectName.toLowercase();
		apexPage.label = objectName + ' Page';
		apexPage.content = EncodingUtil.base64Encode(Blob.valueOf(
			'<apex:page standardController=\''+objectName+'__c\'>'+
				'{!' + objectName + '__c.TestField1__c}' +
				'{!' + objectName + '__c.TestField2__c}' +
			'</apex:page>'));

		// Pass the Metadata items to the job for processing, indicating any dependencies
		MetadataCreateJob.run(
			new List<MetadataCreateJob.Item> {
					new MetadataCreateJob.Item(customObject),
					new MetadataCreateJob.Item(customField1, null, true), // Set wait to true, to process after object creation
					new MetadataCreateJob.Item(customField2),
					new MetadataCreateJob.Item(apexPage, null, true) // Set wait to true, to process after field creation
				},
			new MetadataCreateJob.EmailNotificationMetadataAsyncCallback());
	}

Note: This will email the user when the work is completed. However you can implement your own callback handler if you wish, take a look at the MetadataCreateJob.IMetadataAsyncCallback interface and the email example included.

This is quite a basic example, but starts to illustrate how you could build a more dynamic solution. Perhaps one that takes some form of data input, like a CSV file, which will create the object and fields, before importing the data. Or one that is fired from an Apex Trigger whenever some configuration data in your application is changed?

What will you do with this?

Posted by Andrew Fawcett

Categories: Apex, Metadata API, Tools

Tags:

119 Responses to “Scripting the Apex Metadata API and Batch Apex Support”

  1. Hi Andrew Fawcett,

    Here how to add my object related pagelayout

    MetadataService.Layout layout =
    (MetadataService.Layout) service.readMetadata(‘Layout’,
    new String[] {”rajesh__’+ objectname+’__c’-objectname Layout’ }).getRecords()[0];//Here only problem i think

    My object like:’rajesh__’+ objectname+’__c’ and
    Object creation time created page layout name is: objectname Layout

    help me……..

    By mahesh10811 on June 27, 2014 at 11:56 am

    1. Are you sure the layout name is correct? Check the name with those listed in the Salesforce Workbench, it can list metadata types.

      By Andrew Fawcett on June 27, 2014 at 12:57 pm

  2. Hi Andrew Fawcett,

    Object name:’rajesh__’+ objectName+’__c’//i added but this name also not accepted( __ not accepted)
    pagelayoutname :-objectname layout

    But object null error will comming.please once add my object name below code
    MetadataService.Layout layout =
    (MetadataService.Layout) service.readMetadata(‘Layout’,
    new String[] { ‘Test__c-Test Layout’ }).getRecords()[0];

    System.NullPointerException: Attempt to de-reference a null object
    Error is in expression ‘{!createField}’ in component in page rajesh:createfields

    Class.rajesh.MetadataController11.updateFieldpageLayout: line 882, column 1

    line 882: layout.layoutSections[0]. layoutColumns[0].layoutItems.add(layoutItem);

    Class.rajesh.MetadataController11.createField: line 243, column 1
    line 243: updateFieldpageLayout();//i call above code using this method

    help me …..

    By mahesh10811 on June 27, 2014 at 1:08 pm

  3. Hi Andrew Fawcett,

    I saw Layout name is like below

    objectname__c-objectname Layout

    createdById: 00590000002M4B3AAK
    createdByName: rajesh k
    createdDate: 2014-06-26T08:07:27.000Z
    fileName: layouts/objectname__c-objectname Layout.layout
    fullName: Ramobj__c-objectname Layout

    By mahesh10811 on June 27, 2014 at 1:29 pm

    1. I really want to help you, but the blog post comments are for comments about the blog contents. I will only help you further if you please raise an issue here, https://github.com/financialforcedev/apex-mdapi/issues?state=open.

      By Andrew Fawcett on June 27, 2014 at 1:31 pm

  4. Hi Andrew Fawcett,

    please help me this is only last.

    By mahesh10811 on June 27, 2014 at 1:35 pm

    1. I have a feeling it won’t be, let’s move this chat over to a GitHub issue, it will be much easier to chat there. Thank you!

      By Andrew Fawcett on June 27, 2014 at 1:36 pm

  5. Hi Andrew Fawcett,
    How to perform List operation on MetadataService.LayoutItem .In my layout more than one fields are there that’s why attempt to dereference null object error will came

    By mahesh on June 28, 2014 at 10:27 am

    1. Please ask your questions here, https://github.com/financialforcedev/apex-mdapi/issues?state=open. Thank you.

      By Andrew Fawcett on June 28, 2014 at 1:21 pm

    2. Please ask this question on GitHub, where I can help more easily. Thanks!

      By Andrew Fawcett on July 5, 2014 at 9:45 am

  6. Hi Andy, the new zip folder does not consist of the MetadataCreateJob.cls. I got this error while trying to work the code for batch.
    Error: Compile Error: Invalid type: MetadataCreateJob.Item

    By AngM on July 1, 2015 at 11:42 am

    1. This class is no longer required as create operation is now a sync process and not async. So the class is not needed. You can remove references to it and instead in your code call the new create method which will perform the operation immediatly.

      By Andrew Fawcett on July 1, 2015 at 5:06 pm

  7. Andy,
    This approach works fine for synchronous Apex (VF Controller, or anonymous Apex), but if I try to use this in asynchronous apex (Batch Apex), then I get the following error:

    Web service callout failed: WebService returned a SOAP Fault: INVALID_SESSION_ID: Invalid Session ID found in SessionHeader: Illegal Session faultcode=sf:INVALID_SESSION_ID faultactor=

    Looks like it doesn’t like the Session ID, it gets using UserInfo.getSessionID(), in Batch Apex.

    Thanks for your help!

    -Haroon

    By Haroon Ahmad on July 6, 2015 at 1:01 am

    1. You need to pass your session id into the batch Job

      By Andrew Fawcett on July 6, 2015 at 12:58 pm

      1. That worked beautifully, Andy. I have a need to invoke this batch from an email service. When I try that, I run into the same issue. Can you suggest anyway, I can invoke this successfully, from an email service?

        Thanks!

        By Haroon Ahmad on July 6, 2015 at 3:51 pm

      2. You will need to login again via login Api to get a session, or use an oauth token, or try named credentials. The later I have not tried but could be the best option, might try it this week and blog if it works

        By Andrew Fawcett on July 7, 2015 at 11:56 am

  8. Thanks, Andrew! Will look forward to that, and try it myself as well.

    -Haroon

    By Haroon Ahmad on July 7, 2015 at 5:02 pm

  9. Can i know how to build this for metadata picklist field values to sync to other system from batch process . in this blog batch job class show not available.

    By vijay on November 5, 2020 at 3:22 am

    1. This class got deleted from the repo – it can be found by going back through the repo history. I did however find it in a previously branched repo here – https://github.com/dhawalsaiya/exercise/blob/master/classes/MetadataCreateJob.cls

      By Andrew Fawcett on June 5, 2021 at 7:21 am

Leave a Reply



Mobile Site | Full Site


Get a free blog at WordPress.com Theme: WordPress Mobile Edition by Alex King.