function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
AndrewXAndrewX 

Is it possible to call Metadata API from Apex code? -- Getting Error

Hi All,

 

I would like to be able to call the SF Metadata API from Apex code in my custom app.

 

I went to Setup/App Setup/Develop/API and clicked on 'Download Metadata WSDL' and saved it to my hard drive.

 

Then I went to Setup/App Setup/Develop/ApexClasses and clicked on the 'Generate from WSDL' button. For Step 1, I navigate to the Metadata WSDL that I just saved and click 'Parse WSDL.' This takes me to the Step 2 screen where I then click on the 'Generate Apex code' button.

 

This takes me to the Step 3 screen which shows this error:

 

<error>

Apex generation failed.

Error message:
Error: Class name 'Metadata' already in use. Please edit WSDL to remove repeated names

 

</error>

 

Does this error mean that what I am attempting to do is not allowed? If it is allowed, how do I get around this error.

 

Any help would be appreciated.

 

Thanks,

Andrew

Best Answer chosen by Admin (Salesforce Developers) 
mikefmikef

So your object can be used on lots of different types of user defined sObjects?

Yes you have outlined the perfect use case for metadata api support in Apex. As of now there is no such support.

 

The only way to solve this is to make this a setup/install customization. Not ideal!

 

You can use dynamic apex to consume all sObjects types in your code, so your code can handle any sObject the user wants to use with your functionality.

Sorry I don't have a better answer then this.

 

About you second point what you really need is a custom Polymorphic Id field. I created this idea, please let me know if we need to add anything to it.

All Answers

mikefmikef

You can't call the metadata api in apex, and creating a user defined api won't work either (as you have found out).

 

What do you need to do in you code that you need the metadata api? What is your use case?

Maybe there is a different way to solve the issue you are facing.

AndrewXAndrewX

Hi Mike,

 

Thanks for your fast reply.

 

What I am trying to do is this: My app has a custom object that I want to show up as a Related List on standard objects and on custom objects. For standard objects that's easy -- I just include Lookup fields on my app's custom object that relate to the standard objects.

 

To show my app's Related List on the user's existing custom objects, Lookup fields have to be added to my object for each of those custom objects.  I want the user to be able to select from a list of their existing custom objects to add my app's Related List.

 

So what I want to do is to call the Metadata API to create the Lookup field for those existing custom objects that the user selects.

 

So what I want to do with the Metadata API is to dynamically create a Lookup field on my app's custom object.

 

I am relatively new to Apex coding, and I don't know if there is a more efficient approach. What I initially tried to do is to create one Lookup field on my app's object that would contain the Id of different sorts of objects (standard and custom)--just like the Salesforce Attachment ParentId field does. But it appears that we don't have the ability to create Id fields like that. So as a result, I am having to create a separate Lookup field for every related object.

 

Thanks,

Andrew

mikefmikef

So your object can be used on lots of different types of user defined sObjects?

Yes you have outlined the perfect use case for metadata api support in Apex. As of now there is no such support.

 

The only way to solve this is to make this a setup/install customization. Not ideal!

 

You can use dynamic apex to consume all sObjects types in your code, so your code can handle any sObject the user wants to use with your functionality.

Sorry I don't have a better answer then this.

 

About you second point what you really need is a custom Polymorphic Id field. I created this idea, please let me know if we need to add anything to it.

This was selected as the best answer
AndrewXAndrewX
Thanks, Mike. I suspected that the situation would be as you describe--just needed to confirm. Good 'idea'. I promoted it.
RamyaKrishnaRamyaKrishna

Hello,

 

How can i access report metadata from Metadata API of salesforce?

rk_1978rk_1978

Yes. You can use Ajax Toolkit to call Metadata API calls, but this possible only in VisualForce pages not Apex Class.

 

http://www.salesforce.com/us/developer/docs/ajax/index.htm

LFreelandLFreeland

It is actually possible to generate apex code that will invoke the Metadata API. The problem is that you have to alter the generated "Metadata" wsdl file and when you finally get to the generated apex, you have to alter that as well. The generated apex has to be altered because some of the web service method names are Apex keywords like "update" and "delete" and because of other conflicts as well.

 

I'm assuming that it's possible to run the apex code, but at this point I've only been able to generate the Apex code and save it to my developer org. I'll try to follow up with an another entry when I have time. Hopefully, this helps someone else.

 

Here are some high-level steps that I took to complete the task:

 

1)  Download the Metadata wsdl from salesforce and save it.. This is done by going to Setup --> Develop --> API --> "Generate Metadata WSDL"

 

2) Edit the following in the downloaded wsdl:

      a) Remove the whitespace from the beginning line so that the first line becomes "<?xml version="1.0" encoding="UTF-8"?>"

      b) Scroll to the bottom where you'll find the "Service" element which has a name attribute whose value is "Metadata". This "Metadata" value is what causes the error "Class Name 'Metadata' already in use...", so simply change it to whatever service name you wish and save.

 

3) Now, go to Setup --> App Setup --> Develop --> ApexClasses --> and click "Generate from WSDL". This will generate apex, but it will not be compilable, so copy the generated Apex to either the Force.com IDE in a new class or into a new class window through the salesforce setup UI.

 

4) Alter the generated Apex until it's compiled and saved. You'll have to change some of the method names that are salesforce keywords like "update" and "delete". For example, I changed them to "updateMetadata" and "deleteMetadata"

Andrew2XAndrew2X

Excellent! Thanks!

rk_1978rk_1978

Hi LFreeland,


Can you attach the updated WSDL and Modified Apex Class? 

 

I am still getting following compilation error. Don't know how to fix it.

 

Error: Compile Error: Method does not exist or incorrect signature: WebServiceCallout.invoke(MetadataApexSF.Apex, MetadataApexSF.wsdlToApex_element, MAP<String,MetadataApexSF.wsdlToApexResponse_element>, LIST<String>) at line 33 column 13

 

Thanks,

RK

LFreelandLFreeland

Unfortunately, there's a limit of 20,000 characters to a post and there doesn't appear to be a way to upload files to a post, so I uploaded them to a site that I own (I didn't feel like signing up for another service somewhere). I hope this helps.

 

Metadata WSDL

 

Metadata Apex

 

Luke

mswalehmswaleh

 I tried to use metadata api in apex. i was able to create the apex code with some tweaks in wsdl. but the structure, i believe is not what i expected. If I create the stub in java every metadata type "extends Metadata". but here in apex, it does not.so again we need to change a few thing in the apex code. now after doing all this, when i tried to create a metadata component, say a field, it shows me an error, "Must specify a {http://www.w3.org/2001/XMLSchema-instance}type attribute value for the {http://soap.sforce.com/2006/04/metadata}metadata element".

I tried searching on community but couldn't find any specific solution. could you guide me in right direction pls. Thanks

Marty Y. ChangMarty Y. Chang

mswaleh, I ran into the exact same error, and it frustrated me to no end.  Which service were you trying to invoke?  Based on the sample code given in the Metadata API Developer's Guide for delete(), it seems that if the WSDL is imported into a Java IDE, the invocation works because the code uses strongly typed objects.

 

Ultimately after many attempts to hack to the code to work, and because the code generated by WSDL2Apex (Generate from WSDL button) is not documented at all, I gave up and moved on to other projects.

mswalehmswaleh

Hi Marty, so far I was able to use "create()", "login()", "logout()" and "retrieve" and "checkStatus()" calls. I made some modifications in the code generated by WSDL2Apex using trial and error basis and finally one worked for "create()" call for metadata. in case of "delete()" call, i believe, same could be done.

 

When u create java code from the wsdl, the Metadata is superclass of other components (customfield, customobject etc.) but in apex this is not the case although this thing is defined in the wsdl. what u need to do is

1. make the Metadata class "virtual", so that it can be extended

2. extend the child class to Metadata (e.g. public class CustomField extends Metadata)

3. create field/variable "fullName" in the child class (e.g. customfield). define ints type info and add it to the field order.

4. create field for the type of the metadata compoenet to be set. snippet below.

public String metaType = 'CustomField';
private String[] metaType_att_info = new String[]{'xsi:type'};

5. make the callout normally. it should work. at least worked for me. :)

 

~Swaleh

http://forcecrazy.blogspot.com

kbrewerkbrewer

The retrieve function pulls a zip file, I assumed it would have to pull it to location where it is called from, so in this case it would be SF, which doesn't  allow filesystem access directly.    You were able to call retrieve from an Apex callout and pull the zip file?  Can you say how you did this?

 

Thanks,

Ken

 

I started down the path of callouts to Metadata to try and get a list of fields from a list view so that I could create a dynamic page based on the view.  I ended up making something with fieldsets which is not as flexible and may have to do some javascript as a workaround from not being able to get the metadata information from wthin Salesforce.

Marty Y. ChangMarty Y. Chang

Hello, Ken, I assume you're were actually posting the question to Swaleh.  I'm not sure how to work with ZIP files in Salesforce... but I'm wondering:  Is there a reason you have to use retrieve() instead of listMetadata()?

kbrewerkbrewer

listMetadata appears to only return high level information about the object.  In order to get the criteria and field list of a list view, I believe you have to retrieve it and unzip and read the file.

kbrewerkbrewer

The retrieve function pulls a zip file, I assumed it would have to pull it to location where it is called from, so in this case it would be SF, which doesn't  allow filesystem access directly.    You were able to call retrieve from an Apex callout and pull the zip file?  Can you say how you did this?

 

Thanks,

Ken

mswalehmswaleh

when you do a retrieve() call, the response (in apex) returns the zip file as a String. then you can do something like this (as sfdc cannot let you do something with file system).

ApexMetadataAPI.RetrieveResult rr = meta.checkRetrieveStatus(resultRt.id);

Document doc = new Document();
doc.Body = EncodingUtil.base64Decode(rr.zipFile);
doc.ContentType = 'application/zip';
doc.Description = 'retrieved package at: ' + System.now();
doc.DeveloperName = 'Retrieve_' + System.now().getTime();
doc.FolderId = UserInfo.getUserId();
doc.Name = 'Retrieve_' + System.now().getTime() + '.zip';
insert doc;

 apex is smart enough to insert this Document as zip file. when you open/download this file, you would see the exact folder structure.

 

Let me know if this helps.

 

Best/Swaleh

GoForceGoGoForceGo

So once you retrieve the metadata as ZipFile in Apex, how do you parse it to see the specific metadata information? Apex has no native Zip parsing capabilities and seems like the zip file would have folders - one for each component type (.i.e layout). When I look at http://www.coachamandaxc.com/Salesforce/MetadataFiles/MetadataApex.generated.txt, the ZipFile is String type....

 

The problem I am trying to solve is to get the related list layout for a specific object's layout..Any pointers would be helpful.

 

 

Chirag MehtaChirag Mehta

@mswaleh - Can you please post your example/sample code of create()", "login()", "logout()" and "retrieve" and "checkStatus()" calls. I'm looking specifically for login and create calls. Thanks in advance!

 


mswalehmswaleh

Ken,

for retrieve operation, we retrieve and save the zip to docs. this is to deploy it same/other org. making some changes in the zipped file is not possible in apex, at least for now. so its some like- retrieve, make some offline changes, deploy.

 

to get the component details, you can use listMetadata(). i know this isnt very helpful as gives very high level details but hey this is what we have. no options.:-)

 

let me know if you need any help. Thanks.

 

~Swaleh

Blog

ZoomzoomZoomzoom

Does anybody know if there an Idea was posted to allow to access the Metadata API from Apex? The current way to access it is rather convoluted.

GreenhornGreenhorn

Hi,

 

I am able to create the class generated from WSDL. However I am unable to find how the calls are made to retrieve data.

I need the report type of report. This information is present only in metatadata. Any pointers would be helpful.

 

Thanks.

Apex LearnerApex Learner

Yes you can creat soap envelop in class and let it do its work , example to updating picklist value in field is given below 

 

 

public class soap_envelope 
{

public void update_picklist (string session_id, string client_id, string current_object , string current_field, string new_value){
string SoapBody = '<?xml version="1.0" encoding="UTF-8"?>';
SoapBody += '<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">';
SoapBody += '<S:Header>';
SoapBody += '<SessionHeader xmlns="http://soap.sforce.com/2006/04/metadata"> ';
SoapBody += '<sessionId>'+session_id+'</sessionId>';

SoapBody += '</SessionHeader>';
SoapBody += '<CallOptions xmlns="http://soap.sforce.com/2006/04/metadata">';
SoapBody += '<client>'+client_id+'</client>';
SoapBody += '</CallOptions>';
SoapBody += '</S:Header>';
SoapBody += '<S:Body>';

SoapBody += '<update xmlns="http://soap.sforce.com/2006/04/metadata">';
SoapBody += '<!--Zero or more repetitions:-->';
SoapBody += '<UpdateMetadata xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Customfield">';
SoapBody += '<currentName>'+current_object+'.'+current_field+'</currentName>';

SoapBody += '<metadata xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="CustomField">';
SoapBody += '<!--Optional:-->';
SoapBody += '<fullName>'+current_object+'.'+current_field+'</fullName>';
if (custom =='true') {SoapBody += '<label>number</label>';}


SoapBody += '<picklist>';


SoapBody += '<picklistValues>';
SoapBody += '<fullName>'+new_value+'</fullName>';

SoapBody += '</picklistValues>';
SoapBody += '<sorted>false</sorted>';
SoapBody += '</picklist>';
SoapBody += '<type>Picklist</type>';


SoapBody += '</metadata>';
SoapBody += '</UpdateMetadata>';
SoapBody += '</update>';
SoapBody += '</S:Body>';
SoapBody += '</S:Envelope>';


system.debug('soap envelope============================================='+SoapBody );


HttpRequest req= new HttpRequest(); 
req.setEndpoint('https://ap1-api.salesforce.com/services/Soap/m/25.0'); 
req.setMethod('POST'); 
req.setHeader('content-type', 'text/xml; charset=utf-8'); 
req.setHeader('SOAPAction', 'https://ap1-api.salesforce.com/services/Soap/m/25.0' );
req.setBody(SoapBody);
Http ht = new Http(); 
Httpresponse response = new Httpresponse();
response = ht.send(req);

system.debug('-----------------------------'+response);
// return 'success';
}









}

 

Apex LearnerApex Learner

updated verison with controlling field 

 

public with sharing class soap_envelope1 {
	
	public void update_picklist (string session_id, string client_id, string current_object , string current_field, string new_value, string is_custom, string label, string controlling_field, string controlling_field_value){
    string SoapBody = '<?xml version="1.0" encoding="UTF-8"?>';
    SoapBody += '<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">';
    SoapBody += '<S:Header>';
     SoapBody += '<SessionHeader xmlns="http://soap.sforce.com/2006/04/metadata"> ';
     SoapBody += '<sessionId>'+session_id+'</sessionId>';
    
     SoapBody += '</SessionHeader>';
     SoapBody += '<CallOptions xmlns="http://soap.sforce.com/2006/04/metadata">';
     SoapBody += '<client>'+client_id+'</client>';
     SoapBody += '</CallOptions>';
     SoapBody += '</S:Header>';
     SoapBody += '<S:Body>';
      
      SoapBody += '<update xmlns="http://soap.sforce.com/2006/04/metadata">';
      SoapBody += '<!--Zero or more repetitions:-->';
      SoapBody += '<UpdateMetadata xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Customfield">';
      SoapBody += '<currentName>'+current_object+'.'+current_field+'</currentName>';          
      SoapBody += '<metadata xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="CustomField">';
      SoapBody += '<!--Optional:-->';
      SoapBody += '<fullName>'+current_object+'.'+current_field+'</fullName>';
   if (is_custom =='true') 
   {
      SoapBody += '<label>'+label+'</label>';
   }
      SoapBody += '<picklist>';
                
  if(controlling_field != '-none-')
     {
       soapbody += '<controllingField>'+controlling_field+'</controllingField>';  
     }
       
         SoapBody += '<picklistValues>';
         SoapBody += '<fullName>'+new_value+'</fullName>';
            if (controlling_field_value != '-none-')
            {
              soapbody+= '<controllingFieldValues>'+controlling_field_value+'</controllingFieldValues>';
            }        
          SoapBody += '</picklistValues>';
          SoapBody += '<sorted>false</sorted>';
        SoapBody += '</picklist>';
        SoapBody += '<type>Picklist</type>';
    
                
           SoapBody += '</metadata>';
          SoapBody += '</UpdateMetadata>';
       SoapBody += '</update>';
      SoapBody += '</S:Body>';
    SoapBody += '</S:Envelope>';
    
    
    system.debug('soap envelope============================================='+SoapBody );
    
    
       HttpRequest req= new HttpRequest(); 
            req.setEndpoint('https://ap1-api.salesforce.com/services/Soap/m/25.0'); 
            req.setMethod('POST'); 
            req.setHeader('content-type', 'text/xml; charset=utf-8');  
            req.setHeader('SOAPAction', 'https://ap1-api.salesforce.com/services/Soap/m/25.0' );
            req.setBody(SoapBody);
            Http ht = new Http(); 
            Httpresponse response = new Httpresponse();
            response = ht.send(req);
            
            system.debug('-----------------------------'+response);
           // return 'success';

 }

}

 

GreenhornGreenhorn

Thanks a lot for your reply. I will try that.

OpsterOpster

Were you able to use the APEX code built from the WSDL?

I am looking for a way to grab a SFDC Reports metadata, specifically the Filter Criteria.   I can implement this either in APEX or PHP.  What do you think would be easier?

DellaStreetDellaStreet

A.L.,

When I execute this class, it replaces all of the existing picklist values for the field with the one value submitted, rather than inserting a new value.

In other words, if the values were 

AA

CC

DD

 

before I executed the apex, and I attempted to insert BB, the result is that the only value in the picklist is BB; the former values have been removed.

 

Have you experienced the same issue? Thanks.

ds

Rakesh ERakesh E
i followed the same process and trying call create() method. but i m getting an error message

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=

i didnt find any way to give the authorization value. how can i do that .
please let me know, i have been came to these step after a lot of struggle and now this is blocking me :(

Thank you

Regards,
Rakesh
Rakesh ERakesh E

Hi mswaleh,

 

i followed the same process and trying call create() method. but i m getting an error message

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=

i didnt find any way to give the authorization value. how can i do that .
please let me know, i have been came to these step after a lot of struggle and now this is blocking me :(

Thank you

Regards,
Rakesh

md1md1
Though you'll probably find it when googling, here's the Apex Wrapper by Any Fawcett that solves the probem:
https://github.com/financialforcedev/apex-mdapi