You are here: FME Server Developer's Guide > Developing with the Core APIs > C++ > C++ Tutorial

C++ Tutorial

This documentation serves as a primer for developers that wish to develop applications using the FME Server API. Many common operations such as obtaining a server session, managing repository items and resources, and running workspaces are outlined. Please note that the sample code provided is intended for demonstration purposes only:

API Demo - C++

For demonstration purposes a sample command line application has been created to illustrate many of the common FME Server API concepts with sample code. The sample command line application takes two parameters. The first parameter is the FME Server host name and the second parameter is the FME Server port number. When the command line application is executed, you will be prompted for your credentials. (Leave blank for security-disabled configurations.) Upon verification, a menu is displayed which prompts you to select a given command. Additional information may be required depending on the command selected.

Sample code is available in Java, C++ and .NET. Please refer to the C++ Quick Start for information on how to set up your development environment.

If security is enabled for FME Server, this application's client ID - app_apidemo - is not registered with FME Server, and you must register the client ID for it to work. (See Resources for more information.)

Working with Server Sessions

The first step in working with the FME Server API is to create a server session. To create a server session, you use a factory method as shown below.

IFMEServerSession* serverSession_ = NULL;

FME_createServerSession(serverSession_);

Once a server session is created, you can create the objects required to initialize it. To initialize a server session, a connection info object that specifies the server connection parameters needs to be created as shown below.

IFMEServerConnectionInfo* connectionInfo_

= serverSession_->createServerConnectionInfo(*host, port, *userID, *password);

If security is enabled, then a client ID must also be provided as a session directive. (See Enabling SSL on the Application Server and FME Server Web Service Security Concepts.) The client ID must match the client ID you configured in FME Server.

Note:  Providing a client ID on a security-disabled system will not lead to any negative behavior - the username, password, and client ID will simply be ignored.

IFMEServerStringArray* directives = serverSession_->createServerStringArray();

directives->append(kKeywordClientID);

directives->append("app_apidemo");

serverSession_->init(*connectionInfo_, directives);

Once a server session is initiated, you can now create any additional objects to interact with the FME Server. This includes being able to manage items and resources with the Repository Manager and running workspaces with the Transformation Manager.

An important consideration when working with server sessions is to ensure you release your server session when done. If you initialize server sessions without ever releasing them, you may eventually run out of connection related resources. Releasing a server session is shown below.

serverSession_->disconnect();

Note:  The FME Server API is case insensitive but case preserving for all unique identifiers such as repository name, workspace name, resource name, service name, etc.

Working with Repositories

The Repository Manager component of the FME Server manages repositories. A repository is a central place to store items, resources and other things that enable the FME Server to perform its functions. A typical item is a workspace, a custom format or a custom transformer. Associated with items are resources.

To access a repository, you will need to get the Repository Manager object from the server session as shown below.

IFMEServerRepositoryManager* repositoryMgr_ = serverSession_->getRepositoryManager();

How to list available repositories

The Repository Manager object allows us to get all available repositories.

IFMEServerStatus* status = repositoryMgr_->getRepositories(NULL, *repositories);

if (isSuccess(status))

{

for (FME_UInt32 i = 0; i < repositories->entries(); i++)

{

IFMEServerRepository* repository = (*repositories)(i);

repository->getName(*name);

repository->getDescription(*description);

cout << name->data() << " " << description->data() << endl;

}

}

How to add repositories

Repositories are uniquely identified by their name. A new repository can be created by specifying a new repository name and a repository description.

Note:  If you don't check if a repository with the same name already exists and then add a repository with the same name, you will need to handle either the FME Server exception or error that is returned.

IFMEServerStatus* status = repositoryMgr_->addRepository(*name, *description);

if (isSuccess(status))

{

cout << "Added the repository\n";

}

How to remove repositories

Removing a repository requires the name of the repository. It's important to know that removing a repository will also remove any items and resources in it.

IFMEServerStatus* status = repositoryMgr_->removeRepository(*name, exists);

if (isSuccess(status))

{

cout << "Removed the repository\n";

}

Working with Workspaces

A workspace is a type of repository item so the concepts in this section will also apply to other repository items such as custom formats, custom transformers, and templates. A workspace normally has an FMW file extension, whereas custom formats have a FDS file extension, custom transformers have a FMX file extension, and templates have a .fmwt extension.

FME workspaces are created through FME Workbench and can be published to an FME Server repository so that multiple users can access them. Workspaces in the FME Server repository can also be downloaded, modified, and then published again.

A default installation of FME Server comes with a number of sample workspaces but most users will create their own workspaces. In this section, it's assumed you will either use one of the demo workspaces or create a workspace of your own using FME Workbench.

How to list workspaces in a repository

Workspace names can be quickly listed using repository methods that get the workspace summaries of a workspace. More detailed information about workspaces can also be fetched with other repository methods.

for (FME_UInt32 i = 0; i < repositories->entries(); i++)

{

IFMEServerRepository* repository = (*repositories)(i);

status = repository->getWorkspaceSummaries(NULL, *workspaceSummaries);

if (isSuccess(status))

{

for (FME_UInt32 j = 0; j < workspaceSummaries->entries(); j++)

{

IFMEServerString* workspaceTitle = serverSession_->createServerString();

IFMEServerString* workspaceName = serverSession_->createServerString();

IFMEServerWorkspaceSummary* workspaceSummary = (*workspaceSummaries)(j);

repository->getName(*repositoryName);

workspaceSummary->getTitle(*workspaceTitle);

workspaceSummary->getName(*workspaceName);

cout << "Repository: "<< repositoryName->data() << " " << workspaceTitle->data() << " " << workspaceName->data() << " ON:" << workspaceSummary->getIsEnabled() << endl;

 

serverSession_->destroyServerString(workspaceName);

serverSession_->destroyServerString(workspaceTitle);

}

}

}

How to add workspaces to a repository

To add a new workspace to a repository, the repositosry name, the workspace file path (ie. "C:\myworkspaces\foo.fmw") and the workspace name (ie. foo.fmw) are required. When adding a workspace, the actual workspace file is uploaded to the FME Server repository.

status = repository->addItem(*workspaceName, *workspaceFilePath);

if (isSuccess(status))

{

cout << "Added the workspace\n";

}

How to update workspaces in a repository

To update a workspace in a repository, the repository name, the workspace file path (ie. "C:\myworkspaces\foo.fmw") and the workspace name (ie. foo.fmw) are required. The workspace name uniquely identifies a workspace in a repository and should match the workspace you wish to update. When updating a workspace, the actual workspace file is uploaded to the FME Server repository.

status = repository->updateItem(*workspaceName, *workspaceFilePath);

if (isSuccess(status))

{

cout << "Updated the workspace\n";

}

How to get workspaces from a repository

To get a workspace from a repository, the repository name, the local workspace file path (ie. "C:\myworkspaces\foo.fmw") and the workspace name (ie. foo.fmw) are required. The workspace name uniquely identifies a workspace in a repository and should match the workspace you wish to get. When getting a workspace, the actual workspace file is downloaded from the FME Server repository to the specified local workspace file path.

status = repository->getItem(*workspaceName, *workspaceFilePath, exists);

if (isSuccess(status))

{

cout << "Get workspace succeeded\n";

}

How to remove workspaces in a repository

To remove a workspace in a repository, the repository name and the workspace name are required.

FME_Boolean existsWorkspace = FME_FALSE;

status = repository->removeItem(*workspaceName, existsWorkspace);

if (isSuccess(status))

{

cout << "Removed the workspace\n";

}

Working with Resources

Some workspaces may require resources to successfully run. Any resources associated with a workspace are placed with the workspace and are accessed through the repository object.

How to list resources for a workspace

To view the resources that already exist for a workspace, the repository name and the workspace name are required.

status = repository->getResources(*workspaceName, NULL, *resources);

if (isSuccess(status))

{

for (FME_UInt32 i = 0; i < resources->entries(); i++)

{

IFMEServerString* resourceName = serverSession_->createServerString();

IFMEServerString* resourceDescription = serverSession_->createServerString();

IFMEServerResource* resource = (*resources)(i);

resource->getName(*resourceName);

resource->getDescription(*resourceDescription);

cout << workspaceName->data() << " " << repositoryName->data() << endl;

serverSession_->destroyServerString(resourceDescription);

serverSession_->destroyServerString(resourceName);

}

}

How to add resources for a workspace

To add a new resource for a workspace, the repository name, the workspace name, the resource file path (ie. "C:\myresources\resource.csv) and the resource name (ie. resource.csv) are required. When adding a resource, the actual resource file is uploaded to the FME Server repository.

status = repository->addResource(*workspaceName, *resourceName, *resourceFilePath);

if (isSuccess(status))

{

cout << "Added the resource\n";

}

How to update resources for a workspace

To update a resource for a workspace, the repository name, the workspace name, the resource file path (ie. "C:\myresources\resource.csv") and the resource name (ie. resource.csv) are required. The resource name uniquely identifies a resource for a workspace and should match the resource you wish to update. When updating a resource, the actual resource file is uploaded to the FME Server repository.

status = repository->updateResource(*workspaceName, *resourceName, *resourceFilePath);

if (isSuccess(status))

{

cout << "Updated the resource\n";

}

How to get resources for a workspace

To get a resource for a workspace, the repository name, the workspace name, the local resource file path (ie. "C:\myresources\resource.csv") and the resource name (ie. resource.csv) are required. The resource name uniquely identifies a resource for a workspace and should match the resource you wish to get. When getting a resource, the actual resource file is downloaded from the FME Server repository to the local resource file path.

status = repository->getResource(*workspaceName, *resourceName, *resourceFilePath, exists);

if (isSuccess(status))

{

cout << "Get resource succeeded\n";

}

How to remove resources for a workspace

To remove a specific resource from a workspace, the repository name, workspace name, and resource name are required. Removing a specific workspace will also remove all associated resources for the workspace.

FME_Boolean existsResource = FME_FALSE;

status = repository->removeResource(*workspaceName,*resourceName, existsResource);

if (isSuccess(status))

{

cout << "Removed the resource\n";

}

Working with Services

The default installation of FME Server makes available a number of services, each providing a specific functionality to the end-user. The service object provides you with information about these services such as the service name and service URL pattern. It also allows you to create new service descriptions for services that you may wish to create.

How to list available services

To list the available services the Repository Manager object is required.

IFMEServerServiceVector* services = serverSession_->createServiceVector();

IFMEServerStatus* status = repositoryMgr_->getServices(NULL, *services);

if (isSuccess(status))

{

IFMEServerString* name = serverSession_->createServerString();

IFMEServerString* urlPattern = serverSession_->createServerString();

IFMEServerString* description = serverSession_->createServerString();

IFMEServerString* displayName = serverSession_->createServerString();

for (FME_UInt32 i = 0; i < services->entries(); i++)

{

IFMEServerService* service = (*services)(i);

service->getName(*name);

service->getURLPattern(*urlPattern);

service->getDescription(*description);

service->getDisplayName(*displayName);

cout << name->data()

<< " " << displayName->data()

<< " " << urlPattern->data()

<< " ON:" << service->getIsEnabled()

<< " " << description->data() << endl;

}

serverSession_->destroyServerString(name);

serverSession_->destroyServerString(urlPattern);

serverSession_->destroyServerString(description);

serverSession_->destroyServerString(displayName);

}

serverSession_->destroyServiceVector(services);

How to add services

To add a new service, the service name, service display name, description and URL pattern are required.

status = repositoryMgr_->addService(*service);

if (isSuccess(status))

{

cout << "Added the service\n";

}

How to update services

To add a new service, the service name, service display name, description and URL pattern are required. The service name uniquely identifies a service.

status = repositoryMgr_->updateService(*service);

if (isSuccess(status))

{

cout << "Updated the service\n";

}

How to remove services

To remove a service, only the service name which uniquely identifies the service is required.

IFMEServerStatus* status = repositoryMgr_->removeService(*name, exists);

if (isSuccess(status))

{

if (!exists)

{

cout << "There is no service with this name!" ;

}

}

Running Workspaces

Once a workspace is published to an FME Server repository, the workspace can be run. This can be done by constructing and then submitting a job request using the Transformation Manager object. Normally users would create their own workspaces, but for this example we will be using a demo workspace that is included with the default installation of the FME Server.

First step is to connect to the transformation manager, the component of the FME Server that handles job requests.

IFMEServerTransformationManager* transformationMgr = serverSession_->getTransformationManager();

Next, we need to create a transformation request that will be submitted to the FME Server. The expected parameters are:

Some workspaces have published parameters that can be set. Some have default values but others need to be explicitly set before we are able to run the workspace. These depend on the workspace itself. To set such a published parameter - in this case MAX_FEATURES:

*parmName = "MAX_FEATURES";

*parmValue = "2000";

req->setPublishedParameter(*parmName, *parmValue);

Note:  Values for published parameters must always be passed as strings, regardless of what they actually represent.

There also exist directives that are always available from the FME Server. A list of TM directives can be found in Transformation Manager Directives. We do not need the "tm_" prefix. For our example, we set a high value for the priority of this job, in order to tell the FME Server to execute it before other tasks. This is not necessary but just done to illustrate how to set directives.

*directiveName = "priority";

*directiveValue = "42";

req->setTMDirective(*directiveName, *directiveValue);

Once all parameters and directives have been set, we can submit our request to the FME Server. A transformation request can be submitted either synchronously or asynchronously.

If a job is submitted synchronously, the method call will be blocked until the job has finished. The results are stored in a transformation result object.

IFMEServerTransformationResult* result = serverSession_->createTransformationResult();

transformationMgr->transactJob(*req, *result);

If a job is submitted asynchronously, the method call will immediately return the job ID, which can be used to check if the job has finished. Checking the result of a specified transformation job can be done in a wait-loop until the result is available.

long jobID = 0;

transformationMgr->submitJob(*req, jobID);

FME_Boolean resultAvailable = FME_FALSE;

do

{

transformationMgr->getJobResult(jobID, resultAvailable, *result);

// Wait for 500 milliseconds

Sleep(500);

} while (!resultAvailable);

The transformation result object has a number of methods for obtaining different parts of information from the FME Server response. In our example we first display the raw, unparsed string that was received from the server. In certain scenarios, the clients may want to do their own parsing on this string instead of relying on the provided methods.

result->getFMEServerResponse(*fmeServerResponse);

cout << "Response received:\n" << fmeServerResponse->data() << endl;

We can now display additional pieces of information. First, we check that the transformation was successful and, if so, we list some of the returned properties. Properties are stored as name = value pairs, though some of them may be null.

if (!result->getTransformationSuccess())

{

IFMEServerString* status = serverSession_->createServerString();

result->getStatusMessage(*status);

cout << "Transformation failed.";

cout << "The error was: " << status->data();

serverSession_->destroyServerString(status);

}

else

{

cout << "Transformation successful!\n";

cout << "The following information was parsed from the response:\n";

cout << "============\n";

IFMEServerString* timeReq = serverSession_->createServerString();

IFMEServerString* timeStart = serverSession_->createServerString();

IFMEServerString* timeEnd = serverSession_->createServerString();

IFMEServerString* reqHost = serverSession_->createServerString();

IFMEServerString* reqKwd = serverSession_->createServerString();

FME_Int32 id;

FME_Int32 statNo;

result->getID(id);

result->getTimeRequested(*timeReq);

result->getTimeStarted(*timeStart);

result->getTimeFinished(*timeEnd);

result->getRequesterHost(*reqHost);

result->getRequestKeyword(*reqKwd);

result->getStatusNumber(statNo);

cout

<< "ID: " << id << endl

<< "Time Requested: " << timeReq->data() << endl

<< "Time Started: " << timeStart->data() << endl

<< "Time Finished: " << timeEnd->data() << endl

<< "Request Host: " << reqHost->data() << endl

<< "Request Keyword: " << reqKwd->data() << endl

<< "Status Number: " << statNo << endl;

serverSession_->destroyServerString(timeReq);

serverSession_->destroyServerString(timeStart);

serverSession_->destroyServerString(timeEnd);

serverSession_->destroyServerString(reqHost);

serverSession_->destroyServerString(reqKwd);

}

Safe Software Inc. www.safe.com