Not Xactly. The Summer 07 release does have a meta-data API, which allows you to create objects through code. You could parse the data model, create the appropriate API command, and go from there.
And hey, if you do this in a general way, it could make a great app for AppExchange! <g>
If you guys are willing to point us in the right direction for the meta data API and answer a few remaining questions on UML -> Data Schema mapping, then we'd be able to do it.
Can you point me to documentation on the metadata API please? I'm not very familiar with the Apex platform architecture, but would the metadata API be part of the web services API? If this is the case, then this might be a little heavy weight for what we want to achieve. Ideally we would like to generate something like SQL from our UML model and then import that into Apex. Are there any other routes we should explore?
You can find information on the meta-data API calls in the new API documentation. This is the route you will have to take, since you cannot use DDL SQL with the salesforce.com platform.
You probably also want to take a quick look at this answer in the Apex Code FAQ, which explains some of the ways that SOQL is different from SQL. Although they share a great deal of syntax, they are not identical.
Thanks Ricky. The documentation on the metadata API is useful, it would be extremely useful to have a representative example of using the metadata API. I have written a small program that I thought should work, but I am getting the following error:
AxisFault faultCode: {http://soap.sforce.com/2006/04/metadata}INVALID_SESSION_ID faultSubcode: faultString: INVALID_SESSION_ID: Invalid Session ID found in SessionHeader: Illegal Session
The code is pasted below. I suspect it is something to do with the way that metadata binding is being constructed. Using the regular API, this seems to follow a more logical "login" process. For whatever reason this does not seem to happen for the metadata protocol (see the init method below).
Any help you can offer with this would be much appreciated.
Session management for the metadata api work just like the partner and enterprise apis, the only difference is that we didn't duplicate the login call into the metadata api. Simply use the existing login call from the partner or enterprise API, and use its sessionId on the SessionHeader on the metadata API.
Thanks Simon, I appreciate your response. When I do this I get the same error, but this time the system seems to have an invalid session id for a different reason:
Invalid Session ID found in SessionHeader: Illegal Session. Given final block not properly padded
I am login in using the code in the documentation, the header created during this process is globally scoped and used in the following method:
public void initmeta() throws ServiceException { binding = (MetadataBindingStub) new MetadataServiceLocator().getMetadata(); binding.setUsername(username); binding.setPassword(password); binding.setTimeout(60000);
com.sforce.soap._2006._04.metadata.SessionHeader header = new com.sforce.soap._2006._04.metadata.SessionHeader(); header.setSessionId(sh.getSessionId()); binding.setHeader(new MetadataServiceLocator().getServiceName().getNamespaceURI(),"SessionHeader", header); }
Any thoughts on why this might be happening? Hunting about it looks like it might be do to with security. Could it be something to do with using two independent APIs partner and metadata concurrently?
Thanks Simon, I feel that I am getting warm, but I am now getting the following exception. I have posted the complete code below the exception, I would really appreciate help getting this up and going.
Thanks,
James
LOGGING IN NOW.... AxisFault faultCode: {http://schemas.xmlsoap.org/soap/envelope/}Client faultSubcode: faultString: No operation available for request {http://soap.sforce.com/2006/04/metadata}create faultActor: faultNode: faultDetail: {http://xml.apache.org/axis/}stackTrace:No operation available for request {http://soap.sforce.com/2006/04/metadata}create at org.apache.axis.message.SOAPFaultBuilder.createFault(SOAPFaultBuilder.java:222)
public void initmeta() throws ServiceException { binding = (MetadataBindingStub) new MetadataServiceLocator().getMetadata(); binding.setHeader(new MetadataServiceLocator().getServiceName().getNamespaceURI(),"SessionHeader", sessionheader); binding._setProperty(SoapBindingStub.ENDPOINT_ADDRESS_PROPERTY, loginResult.getServerUrl()); binding.setMaintainSession(true); }
private boolean login() throws ServiceException {
soapbinding = (SoapBindingStub) new SforceServiceLocator().getSoap();
// Time out after a minute soapbinding.setTimeout(60000); // Test operation try { System.out.println("LOGGING IN NOW...."); loginResult = soapbinding.login(username, password); } catch (LoginFault ex) { // The LoginFault derives from AxisFault ExceptionCode exCode = ex.getExceptionCode(); if (exCode == ExceptionCode.FUNCTIONALITY_NOT_ENABLED || exCode == ExceptionCode.INVALID_CLIENT || exCode == ExceptionCode.INVALID_LOGIN || exCode == ExceptionCode.LOGIN_DURING_RESTRICTED_DOMAIN || exCode == ExceptionCode.LOGIN_DURING_RESTRICTED_TIME || exCode == ExceptionCode.ORG_LOCKED || exCode == ExceptionCode.PASSWORD_LOCKOUT || exCode == ExceptionCode.SERVER_UNAVAILABLE || exCode == ExceptionCode.TRIAL_EXPIRED || exCode == ExceptionCode.UNSUPPORTED_CLIENT) { System.out.println("Please be sure that you have a valid username and password."); } else { // Write the fault code to the console System.out.println(ex.getExceptionCode()); // Write the fault message to the console System.out.println("An unexpected error has occurred." + ex.getMessage()); } return false; } catch (Exception ex) { System.out.println("An unexpected error has occurred: " + ex.getMessage()); ex.printStackTrace(); return false; } // Check if the password has expired if (loginResult.isPasswordExpired()) { System.out.println("An error has occurred. Your password has expired."); return false; } /** Once the client application has logged in successfully, it will use * the results of the login call to reset the endpoint of the service * to the virtual server instance that is servicing your organization. * To do this, the client application sets the ENDPOINT_ADDRESS_PROPERTY * of the binding object using the URL returned from the LoginResult. */ soapbinding._setProperty(SoapBindingStub.ENDPOINT_ADDRESS_PROPERTY, loginResult.getServerUrl()); /** The sample client application now has an instance of the SoapBindingStub * that is pointing to the correct endpoint. Next, the sample client application * sets a persistent SOAP header (to be included on all subsequent calls that * are made with the SoapBindingStub) that contains the valid sessionId * for our login credentials. To do this, the sample client application * creates a new SessionHeader object and set its sessionId property to the * sessionId property from the LoginResult object. */ // Create a new session header object and add the session id // from the login return object
sessionheader = new SessionHeader(); sessionheader.setSessionId(loginResult.getSessionId());
/** Next, the sample client application calls the setHeader method of the * SoapBindingStub to add the header to all subsequent method calls. This * header will persist until the SoapBindingStub is destroyed until the header * is explicitly removed. The "SessionHeader" parameter is the name of the * header to be added. */ // set the session header for subsequent call authentication soapbinding.setHeader(new SforceServiceLocator().getServiceName().getNamespaceURI(),"SessionHeader", sessionheader); // return true to indicate that we are logged in, pointed // at the right url and have our security token in place.
According to the generated API no field values can be nillable. So if, for instance, you omit the display format on a CustomField, you will get the following error:
java.io.IOException: Non nillable element 'displayFormat' is null
However if this value is set, the following error occurs:
faultString: null: Cannot specify a displayFormat for a Text field
To fix this Catch-22 situation, it is necessary to edit the generated code for CustomField and CustomObject to set appropriate fields to nillable.
I have figured this out. So others know, CustomFields are bound to objects based on their full name. Given an object with the name "O1__S", a CustomField F1 can be defined on that object by naming the custom field "O1__S.F1".
We are finding that when we programmatically create CustomFields as described, the default "Accessibility" is set to none. Can this property be set through the metadata API?
The last message is in this thread is a month old so I assume the problem was fixed. I ran into the same error (No operation available for request...) and wanted to post the solution for the benefit of future developers who may run into this issue.
The trick is to set the ENDPOINT_ADDRESS_PROPETY of MetadataBindingStub to LoginResult.getMetadataServerUrl() instead of LoginResult.getServerUrl() used for normal Apex calls. Here is what your log-in code for Metadata API calls should look like:
LoginResult loginResult = "... normal Apex API code to obtain LoginResult..."; MetadataBindingStub metaBinding = (MetadataBindingStub) new MetadataServiceLocator().getMetadata(); metaBinding._setProperty(MetadataBindingStub.ENDPOINT_ADDRESS_PROPERTY, loginResult.getMetadataServerUrl()); SessionHeader sh = new SessionHeader(); sh.setSessionId(loginResult.getSessionId()); metaBinding.setHeader(new MetadataServiceLocator().getServiceName() .getNamespaceURI(), "SessionHeader", sh);
Not Xactly. The Summer 07 release does have a meta-data API, which allows you to create objects through code. You could parse the data model, create the appropriate API command, and go from there.
And hey, if you do this in a general way, it could make a great app for AppExchange! <g>
- Rick Greenwald
Yes, it would make a good app wouldn't it.
If you guys are willing to point us in the right direction for
the meta data API and answer a few remaining questions
on UML -> Data Schema mapping, then we'd be able to
do it.
Andy
Can you point me to documentation on the metadata API please? I'm not very familiar with the Apex platform architecture, but would the metadata API be part of the web services API? If this is the case, then this might be a little heavy weight for what we want to achieve. Ideally we would like to generate something like SQL from our UML model and then import that into Apex. Are there any other routes we should explore?
Thanks,
James
AxisFault
faultCode: {http://soap.sforce.com/2006/04/metadata}INVALID_SESSION_ID
faultSubcode:
faultString: INVALID_SESSION_ID: Invalid Session ID found in SessionHeader: Illegal Session
The code is pasted below. I suspect it is something to do with the way that metadata binding is being constructed. Using the regular API, this seems to follow a more logical "login" process. For whatever reason this does not seem to happen for the metadata protocol (see the init method below).
Any help you can offer with this would be much appreciated.
public class Create {
private String username = "****";
private String password = "****";
private MetadataBindingStub binding;
public static void main(String[] args) throws ServiceException {
Create create = new Create();
create.run();
}
public void addObject() {
// create the name field for the custom object
CustomField namefield = new CustomField();
namefield.setFullName("TestObject name");
namefield.setCaseSensitive(false);
namefield.setDefaultValue("");
namefield.setDescription("This is name field");
namefield.setDisplayFormat("Display");
namefield.setExternalId(false);
namefield.setFormula("");
namefield.setLabel("name field");
namefield.setLength(15);
namefield.setPicklist(new Picklist());
namefield.setPopulateExistingRows(false);
namefield.setPrecision(0);
namefield.setReferenceTo("");
namefield.setRelationshipName("");
namefield.setRequired(true);
namefield.setScale(0);
namefield.setStartingNumber(0);
namefield.setType(FieldType.Text);
namefield.setUnique(false);
// create the custom object
CustomObject cobject = new CustomObject();
cobject.setFullName("TestObject");
cobject.setDeploymentStatus(DeploymentStatus.Deployed);
cobject.setDescription("This is TestObject");
cobject.setEnableActivities(false);
cobject.setEnableDivisions(false);
cobject.setEnableHistory(false);
cobject.setEnableReports(false);
cobject.setGender(Gender.Neuter);
cobject.setLabel("TestObject label");
cobject.setNameField(namefield);
cobject.setPluralLabel("");
cobject.setSharingModel(SharingModel.ReadWrite);
cobject.setStartsWith(StartsWith.Consonant);
// update the platform
Metadata[] metadata = new Metadata[] { cobject };
try {
binding.create(metadata);
}
catch(RemoteException re) {
re.printStackTrace();
}
}
public void init() throws ServiceException {
binding = (MetadataBindingStub) new MetadataServiceLocator().getMetadata();
binding.setUsername(username);
binding.setPassword(password);
}
public void run() throws ServiceException {
init();
addObject();
}
}
Invalid Session ID found in SessionHeader: Illegal Session. Given final block not properly padded
I am login in using the code in the documentation, the header created during this process is globally scoped and used in the following method:
public void initmeta() throws ServiceException {
binding = (MetadataBindingStub) new MetadataServiceLocator().getMetadata();
binding.setUsername(username);
binding.setPassword(password);
binding.setTimeout(60000);
com.sforce.soap._2006._04.metadata.SessionHeader header = new com.sforce.soap._2006._04.metadata.SessionHeader();
header.setSessionId(sh.getSessionId());
binding.setHeader(new MetadataServiceLocator().getServiceName().getNamespaceURI(),"SessionHeader", header);
}
Any thoughts on why this might be happening? Hunting about it looks like it might be do to with security. Could it be something to do with using two independent APIs partner and metadata concurrently?
Thanks,
James
Message Edited by jwillans on 08-03-200708:48 AM
Message Edited by jwillans on 08-03-200708:52 AM
You also need to set the endpointUrl to the metadataServerUrl returned in the login Result.
Thanks,
James
LOGGING IN NOW....
AxisFault
faultCode: {http://schemas.xmlsoap.org/soap/envelope/}Client
faultSubcode:
faultString: No operation available for request {http://soap.sforce.com/2006/04/metadata}create
faultActor:
faultNode:
faultDetail:
{http://xml.apache.org/axis/}stackTrace:No operation available for request {http://soap.sforce.com/2006/04/metadata}create
at org.apache.axis.message.SOAPFaultBuilder.createFault(SOAPFaultBuilder.java:222)
import java.rmi.RemoteException;
import javax.xml.rpc.ServiceException;
import com.sforce.soap._2006._04.metadata.CustomField;
import com.sforce.soap._2006._04.metadata.CustomObject;
import com.sforce.soap._2006._04.metadata.DeploymentStatus;
import com.sforce.soap._2006._04.metadata.FieldType;
import com.sforce.soap._2006._04.metadata.Gender;
import com.sforce.soap._2006._04.metadata.Metadata;
import com.sforce.soap._2006._04.metadata.MetadataBindingStub;
import com.sforce.soap._2006._04.metadata.MetadataServiceLocator;
import com.sforce.soap._2006._04.metadata.Picklist;
import com.sforce.soap._2006._04.metadata.SharingModel;
import com.sforce.soap._2006._04.metadata.StartsWith;
import com.sforce.soap.partner.LoginResult;
import com.sforce.soap.partner.SessionHeader;
import com.sforce.soap.partner.SforceServiceLocator;
import com.sforce.soap.partner.SoapBindingStub;
import com.sforce.soap.partner.fault.ExceptionCode;
import com.sforce.soap.partner.fault.LoginFault;
public class Create {
private String username = "***";
private String password = "***";
private SessionHeader sessionheader;
private LoginResult loginResult;
private SoapBindingStub soapbinding;
private MetadataBindingStub binding;
public static void main(String[] args) throws ServiceException {
Create create = new Create();
create.run();
}
public void addObject() {
// create the name field for the custom object
CustomField namefield = new CustomField();
namefield.setFullName("TestObject name");
namefield.setCaseSensitive(false);
namefield.setDefaultValue("");
namefield.setDescription("This is name field");
namefield.setDisplayFormat("Display");
namefield.setExternalId(false);
namefield.setFormula("");
namefield.setLabel("name field");
namefield.setLength(15);
namefield.setPicklist(new Picklist());
namefield.setPopulateExistingRows(false);
namefield.setPrecision(0);
namefield.setReferenceTo("");
namefield.setRelationshipName("");
namefield.setRequired(true);
namefield.setScale(0);
namefield.setStartingNumber(0);
namefield.setType(FieldType.Text);
namefield.setUnique(false);
// create the custom object
CustomObject cobject = new CustomObject();
cobject.setFullName("TestObject");
cobject.setDeploymentStatus(DeploymentStatus.Deployed);
cobject.setDescription("This is TestObject");
cobject.setEnableActivities(false);
cobject.setEnableDivisions(false);
cobject.setEnableHistory(false);
cobject.setEnableReports(false);
cobject.setGender(Gender.Neuter);
cobject.setLabel("TestObject label");
cobject.setNameField(namefield);
cobject.setPluralLabel("");
cobject.setSharingModel(SharingModel.ReadWrite);
cobject.setStartsWith(StartsWith.Consonant);
// update the platform
Metadata[] metadata = new Metadata[] { cobject, namefield };
try {
binding.create(metadata);
}
catch(RemoteException re) {
re.printStackTrace();
}
}
public void initmeta() throws ServiceException {
binding = (MetadataBindingStub) new MetadataServiceLocator().getMetadata();
binding.setHeader(new MetadataServiceLocator().getServiceName().getNamespaceURI(),"SessionHeader", sessionheader);
binding._setProperty(SoapBindingStub.ENDPOINT_ADDRESS_PROPERTY, loginResult.getServerUrl());
binding.setMaintainSession(true);
}
private boolean login() throws ServiceException {
soapbinding = (SoapBindingStub) new SforceServiceLocator().getSoap();
// Time out after a minute
soapbinding.setTimeout(60000);
// Test operation
try {
System.out.println("LOGGING IN NOW....");
loginResult = soapbinding.login(username, password);
}
catch (LoginFault ex) {
// The LoginFault derives from AxisFault
ExceptionCode exCode = ex.getExceptionCode();
if (exCode == ExceptionCode.FUNCTIONALITY_NOT_ENABLED ||
exCode == ExceptionCode.INVALID_CLIENT ||
exCode == ExceptionCode.INVALID_LOGIN ||
exCode == ExceptionCode.LOGIN_DURING_RESTRICTED_DOMAIN ||
exCode == ExceptionCode.LOGIN_DURING_RESTRICTED_TIME ||
exCode == ExceptionCode.ORG_LOCKED ||
exCode == ExceptionCode.PASSWORD_LOCKOUT ||
exCode == ExceptionCode.SERVER_UNAVAILABLE ||
exCode == ExceptionCode.TRIAL_EXPIRED ||
exCode == ExceptionCode.UNSUPPORTED_CLIENT) {
System.out.println("Please be sure that you have a valid username and password.");
} else {
// Write the fault code to the console
System.out.println(ex.getExceptionCode());
// Write the fault message to the console
System.out.println("An unexpected error has occurred." + ex.getMessage());
}
return false;
} catch (Exception ex) {
System.out.println("An unexpected error has occurred: " + ex.getMessage());
ex.printStackTrace();
return false;
}
// Check if the password has expired
if (loginResult.isPasswordExpired()) {
System.out.println("An error has occurred. Your password has expired.");
return false;
}
/** Once the client application has logged in successfully, it will use
* the results of the login call to reset the endpoint of the service
* to the virtual server instance that is servicing your organization.
* To do this, the client application sets the ENDPOINT_ADDRESS_PROPERTY
* of the binding object using the URL returned from the LoginResult.
*/
soapbinding._setProperty(SoapBindingStub.ENDPOINT_ADDRESS_PROPERTY,
loginResult.getServerUrl());
/** The sample client application now has an instance of the SoapBindingStub
* that is pointing to the correct endpoint. Next, the sample client application
* sets a persistent SOAP header (to be included on all subsequent calls that
* are made with the SoapBindingStub) that contains the valid sessionId
* for our login credentials. To do this, the sample client application
* creates a new SessionHeader object and set its sessionId property to the
* sessionId property from the LoginResult object.
*/
// Create a new session header object and add the session id
// from the login return object
sessionheader = new SessionHeader();
sessionheader.setSessionId(loginResult.getSessionId());
/** Next, the sample client application calls the setHeader method of the
* SoapBindingStub to add the header to all subsequent method calls. This
* header will persist until the SoapBindingStub is destroyed until the header
* is explicitly removed. The "SessionHeader" parameter is the name of the
* header to be added.
*/
// set the session header for subsequent call authentication
soapbinding.setHeader(new SforceServiceLocator().getServiceName().getNamespaceURI(),"SessionHeader", sessionheader);
// return true to indicate that we are logged in, pointed
// at the right url and have our security token in place.
soapbinding.setMaintainSession(true);
return true;
}
public void run() throws ServiceException {
if(login()) {
initmeta();
addObject();
}
}
}
public void initmeta() throws ServiceException {
binding = (MetadataBindingStub) new MetadataServiceLocator().getMetadata();
binding.setHeader(new MetadataServiceLocator().getServiceName().getNamespaceURI(),"SessionHeader", sessionheader);
binding.setMaintainSession(true);
}
The metadata API is *not* correctly generating whether elements can be nilable or not as specified in:
http://www.salesforce.com/us/developer/docs/api/index.htm
According to the generated API no field values can be nillable. So if, for instance, you omit the display format on a CustomField, you will get the following error:
java.io.IOException: Non nillable element 'displayFormat' is null
However if this value is set, the following error occurs:
faultString: null: Cannot specify a displayFormat for a Text field
To fix this Catch-22 situation, it is necessary to edit the generated code for CustomField and CustomObject to set appropriate fields to nillable.
James
Thanks,
James
James
James
Thanks,
James
Also, is there a way of creating Custom Tabs through the metadata API (or any API for that matter)?
Thanks,
James
The trick is to set the ENDPOINT_ADDRESS_PROPETY of MetadataBindingStub to LoginResult.getMetadataServerUrl() instead of LoginResult.getServerUrl() used for normal Apex calls. Here is what your log-in code for Metadata API calls should look like:
LoginResult loginResult = "... normal Apex API code to obtain LoginResult...";
MetadataBindingStub metaBinding = (MetadataBindingStub) new MetadataServiceLocator().getMetadata();
metaBinding._setProperty(MetadataBindingStub.ENDPOINT_ADDRESS_PROPERTY, loginResult.getMetadataServerUrl());
SessionHeader sh = new SessionHeader();
sh.setSessionId(loginResult.getSessionId());
metaBinding.setHeader(new MetadataServiceLocator().getServiceName()
.getNamespaceURI(), "SessionHeader", sh);
Max