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
XactAndyXactAndy 

Importing data/uml models

Hello again,

Is there any way that you can import a data model or UML model into
Apex and thus automatically create Custom objects (database tables)?

Thanks,

Andy
RickyGRickyG
Andy -

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
XactAndyXactAndy
Hi Rick,

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
jwillansjwillans
Ricky,

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
RickyGRickyG
James -
 
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.
 
Hope this helps.
 
- Rick Greenwald
Developer Evangelist
jwillansjwillans
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.

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();
}

}
SuperfellSuperfell
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.
jwillansjwillans
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,

James

Message Edited by jwillans on 08-03-200708:48 AM

Message Edited by jwillans on 08-03-200708:52 AM

SuperfellSuperfell
Don't set the username & password properties on the binding.

You also need to set the endpointUrl to the metadataServerUrl returned in the login Result.
jwillansjwillans
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)


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();
}
}

}
jwillansjwillans
Success! The code is exactly as before, but with the following modified initmeta (the endpoint addresss is not set):

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
jwillansjwillans
I'd like to now understand how to add CustomFields to CustomObjects, it is not obvious how this should be done?

Thanks,

James
jwillansjwillans
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".

James
SuperfellSuperfell
That's right. I'll get the docs updated with a clear description of this.
jwillansjwillans
Thanks Simon, also I would appreciate it if you could take into account my note about "nillable" fields being wrongly generated.

James
jwillansjwillans
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?

Thanks,

James
jwillansjwillans
Bounce! We are struggling with the default "Accessibility" set to none when creating CustomFields. Is there any way of avoiding this?

Also, is there a way of creating Custom Tabs through the metadata API (or any API for that matter)?

Thanks,

James
MaximusMaximus
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);

Max