Closed ghost closed 10 years ago
That is strange, i assume the same user can create the Apex class manually?
Yes and its in a without sharing class
Von meinem iPhone gesendet
Am 04.04.2014 um 18:40 schrieb Andrew Fawcett notifications@github.com:
That is strange, i assume the same user can create the Apex class manually?
— Reply to this email directly or view it on GitHub.
Have you tried with a simple HelloWorld class? I've submitted a small example just to compare https://github.com/afawcett/apex-toolingapi/blob/master/apex-toolingapi/src/classes/ToolingAPIDemo.cls#L29
A simple class works. Only if I reference packaged object it fails.
Von meinem iPhone gesendet
Am 09.04.2014 um 10:50 schrieb Andrew Fawcett notifications@github.com:
Have you tried with a simple HelloWorld class? I've submitted a small example just to compare https://github.com/afawcett/apex-toolingapi/blob/master/apex-toolingapi/src/classes/ToolingAPIDemo.cls#L29
— Reply to this email directly or view it on GitHub.
Ok, let me extend me test to this... Thanks.
This looks like a bug in the Tooling API itself, I've reproduced it Developer Workbench. I'll raise a case, meanwhile I think this can be worked around, by doing what i suspect the Developer Console is doing, notice when you do the same here, it precreates an empty class then updates it, going to try an emulate this in code next.
{
"body": "public class HelloWorld { public void hello() { packagea__WorkOrders__c lookup; } }",
"name": "HelloWorld",
"status": "Active",
"fullName": "HelloWorld",
"metadata": {
"status": "Active",
"packageVersions": [
{
"namespace": "packagea",
"minorNumber": 2,
"majorNumber": 1
}
],
"apiVersion": 28
}
}
This is my test Ape code that generated the above request...
public static void createClass() {
// Constructs the Tooling API wrapper (default constructor uses user session Id)
ToolingAPI toolingAPI = new ToolingAPI();
ToolingAPI.ApexClass apexClass = new ToolingAPI.ApexClass();
apexClass.Name = 'HelloWorld';
apexClass.FullName = 'HelloWorld';
apexClass.Body = 'public class HelloWorld { public void hello() { packagea__WorkOrders__c lookup; } }';
apexClass.Status = 'Active';
apexClass.metadata = new ToolingAPI.ApexClassMetadata();
apexClass.metadata.apiVersion = 28.0;
apexClass.metadata.status = 'Active';
apexClass.metadata.packageVersions = new List<ToolingAPI.PackageVersion>();
apexClass.metadata.packageVersions.add(new ToolingAPI.PackageVersion());
apexClass.metadata.packageVersions[0].majorNumber = 1;
apexClass.metadata.packageVersions[0].minorNumber = 2;
apexClass.metadata.packageVersions[0].namespace = 'packagea';
toolingAPI.createSObject(apexClass);
}
Doing HTTP POST with skeleton class, then HTTP PATCH on the ApexClass object, with a managed field reference also fails (and I also suspect HTTP PATCH on ApexClass is broken generally, see below)....
public static void createAndUpdateClass() {
// Constructs the Tooling API wrapper (default constructor uses user session Id)
ToolingAPI toolingAPI = new ToolingAPI();
// Create class
ToolingAPI.ApexClass apexClass = new ToolingAPI.ApexClass();
apexClass.Name = 'HelloWorld';
apexClass.FullName = 'HelloWorld';
apexClass.Body = 'public class HelloWorld { }';
apexClass.Status = 'Active';
apexClass.metadata = new ToolingAPI.ApexClassMetadata();
apexClass.metadata.apiVersion = 28.0;
apexClass.metadata.status = 'Active';
apexClass.metadata.packageVersions = new List<ToolingAPI.PackageVersion>();
apexClass.metadata.packageVersions.add(new ToolingAPI.PackageVersion());
apexClass.metadata.packageVersions[0].majorNumber = 1;
apexClass.metadata.packageVersions[0].minorNumber = 2;
apexClass.metadata.packageVersions[0].namespace = 'packagea';
ToolingAPI.SaveResult sr = toolingAPI.createSObject(apexClass);
// Update class
apexClass = new ToolingAPI.ApexClass();
apexClass.Id = sr.Id;
apexClass.Body = 'public class HelloWorld { public void hello() { packagea__WorkOrders__c lookup; } }';
toolingAPI.updateSObject(apexClass);
}
Again via Developer Workbench...
{
"body": "public class HelloWorld { public void hello() { packagea__WorkOrders__c lookup; } }"
}
Interestingly.... even without the managed field reference HTTP PATCH fails with the same error...
{
"body": "public class HelloWorld { public void hello() { } }"
}
This i assume is another issue with doing HTTP PATCH on an ApexClass, perhaps what is ment in the Salesforce documentation as the following, it just happens to be the same error...
https://www.salesforce.com/us/developer/docs/api/Content/sforce_api_objects_apexclass.htm
Although Apex classes and triggers have the Create and Update field properties, a runtime exception occurs if you try to create or update them using the API. Instead, use the Force.com Migration Tool, the Salesforce user interface, or the Force.com IDE to create or update Apex classes or triggers.
Though clearly we have been able to 'create' via the Tooling API, and the above is from the standard Salesforce API docs, confusing, since in respect to 'create' the behaviour is different depending on Salesforce API and Tooling API. Next thing to try, in respect to updating an Apex class with a managed field reference is via ApexClassMember object, which brings with it quite a lot of overhead and aysnc sadly...
Ok this rather lengthy process works!
public static void createAndUpdateClass() {
// Constructs the Tooling API wrapper (default constructor uses user session Id)
ToolingAPI tooling = new ToolingAPI();
// Create class
ToolingAPI.ApexClass apexClass = new ToolingAPI.ApexClass();
apexClass.Name = 'HelloWorld';
apexClass.FullName = 'HelloWorld';
apexClass.Body = 'public class HelloWorld { }';
apexClass.Status = 'Active';
apexClass.metadata = new ToolingAPI.ApexClassMetadata();
apexClass.metadata.apiVersion = 28.0;
apexClass.metadata.status = 'Active';
apexClass.metadata.packageVersions = new List<ToolingAPI.PackageVersion>();
apexClass.metadata.packageVersions.add(new ToolingAPI.PackageVersion());
apexClass.metadata.packageVersions[0].majorNumber = 1;
apexClass.metadata.packageVersions[0].minorNumber = 2;
apexClass.metadata.packageVersions[0].namespace = 'packagea';
ToolingAPI.SaveResult sr = tooling.createSObject(apexClass);
// Delete MetadataContainer
List<ToolingAPI.MetadataContainer> containers =
(List<ToolingAPI.MetadataContainer>)
tooling.query(
'SELECT Id, Name FROM MetadataContainer WHERE Name = \'UpdateHelloWorld\'').records;
if ( containers != null && ! containers.isEmpty() )
tooling.deleteSObject(ToolingAPI.SObjectType.MetadataContainer, containers[0].Id);
// Create MetadataContainer
ToolingAPI.MetadataContainer container = new ToolingAPI.MetadataContainer();
container.name = 'UpdateHelloWorld';
ToolingAPI.SaveResult containerSaveResult = tooling.createSObject(container);
Id containerId = containerSaveResult.id;
// Create ApexClassMember and associate them with the MetadataContainer
ToolingAPI.ApexClassMember apexClassMember = new ToolingAPI.ApexClassMember();
apexClassMember.Body = 'public class HelloWorld { public void hello() { packagea__WorkOrders__c lookup; } }';
apexClassMember.ContentEntityId = sr.id;
apexClassMember.MetadataContainerId = containerId;
ToolingAPI.SaveResult apexClassMemberSaveResult = tooling.createSObject(apexClassMember);
// Create ContainerAysncRequest to deploy the Apex Classes
ToolingAPI.ContainerAsyncRequest asyncRequest = new ToolingAPI.ContainerAsyncRequest();
asyncRequest.metadataContainerId = containerId;
asyncRequest.IsCheckOnly = false;
ToolingAPI.SaveResult asyncRequestSaveResult = tooling.createSObject(asyncRequest);
// The above starts an async background compile, the following needs repeated (polled) to confirm compilation
ToolingApi.ContainerAsyncRequest containerAsyncRequest =
((List<ToolingAPI.ContainerAsyncRequest>)
tooling.query(
'SELECT Id, State, MetadataContainerId, CompilerErrors ' +
'FROM ContainerAsyncRequest ' +
'WHERE Id = \'' + asyncRequestSaveResult.Id + '\'').records)[0];
System.debug('State is ' + containerAsyncRequest.State);
}
This outputs...
11:25:23.187 (2187009136)|USER_DEBUG|[83]|DEBUG|State is Queued
Note that at the end here, one really needs to poll the status (via apex:actionPoller or Batch Apex as per Apex Metadata API approach). ContainerAsyncRequest object to determine the result of the compilation. I just waited a while and checked manually the contents of the class and sure enough the managed field reference was present, thus confirming the above does work.
Is this really important for creating a class?
apexClass.metadata = new ToolingAPI.ApexClassMetadata();
apexClass.metadata.apiVersion = 28.0;
apexClass.metadata.status = 'Active';
apexClass.metadata.packageVersions = new List<ToolingAPI.PackageVersion>();
apexClass.metadata.packageVersions.add(new ToolingAPI.PackageVersion());
apexClass.metadata.packageVersions[0].majorNumber = 1;
apexClass.metadata.packageVersions[0].minorNumber = 2;
apexClass.metadata.packageVersions[0].namespace = 'packagea';
Please keep me (on this issue) posted when Salesforce.com replied to you case.
As I understand you answers (thanks a lot) there is no such thing as create and populate a class by clicking a button as it always involves batching/waiting/polling? :-(
What do you mean by using apex:poller? It sound like a visualforce only solution without batch. I would prefer that as this class generation is called by a custom button from a page.
FYI, I have updated the repo with a more generic example, https://github.com/afawcett/apex-toolingapi/blob/master/apex-toolingapi/src/classes/ToolingAPIDemo.cls#L29
Sorry, ment to say apex:actionPoller, and yes that is correct i see no option currently that does not involve batching/waiting/polling as you say.
You could still use apex:actionPoller from your custom button apex page, just make the calls to perform the code in the 'action' method on page load (or from confirmation button on page), then retain in view state the async id and use apex:actionPoller to check for the status. Check out my blog on Apex Metadata API for how this is done in that context its the same flow.
And no, the metadata stuff was only me trying to see if it made a difference, the general example i have committed does not have this. :+1:
If, in the meantime, if you are looking for a Metadata API approach via a Custom Button check this out, https://github.com/afawcett/declarative-lookup-rollup-summaries/blob/master/rolluptool/src/classes/RollupController.cls
The case has been escalated to Salesforce R&D.
I'll close this issue, since when/if Salesforce fix this i believe the library will just start supporting this also.
A similar issue has been found and an Idea raised here, https://success.salesforce.com/ideaView?id=08730000000l8a5AAA&sort=2
I have a managed (in my ISV package) class that creates an unmanaged class (outside of the package) based on database data.
When I do:
it fails with the error:
The entity it does not seem to have access to is an SObject of a packaged type, so not a class. So I see no reason why someone outside should not be able to use and compile a reference to such a record.