The IOrganizationPartyManagementGET Apex interface provides extensibility for GET operations through Apex pre- and post-hooks and GraphQL query customization. Implementations can validate incoming requests, modify query structures, and refine the response to support business-specific retrieval rules while maintaining TMF-compliant behavior.
This interface supports the following hooks.
- transformRequest
- configureDefaultValidations
- applyCustomValidations
- customiseGraphQLQuery
- handlePostOperation
Retrieve Operation Use Cases
| Use Case | Hook(s) | Description | Example Scenarios | Benefit |
|---|---|---|---|---|
| External-ID Resolution | transformRequest |
Transforms a legacy or external identifier into the canonical resource ID expected by the retrieve operation and adds it to the shared request context as { id }. |
- Resolve a legacy customer key to a Contact ID<br>- Map a partner-system identifier to { id }<br>- Normalize identifiers from migrated systems <br>- Translate an account-specific reference into the canonical ID |
Allows legacy clients and external integrations to retrieve resources using familiar identifiers without changing the shared API contract. |
| Role-Based or Regional Access Control | applyCustomValidations |
Applies authorization and data-governance rules before the query executes. Access can be evaluated using the caller's role, region, business unit, channel, or other request context. | - Restrict access to customers outside the user's assigned region <br>- Permit only supervisors to retrieve high-value customer records <br>- Block partner channels from accessing internal-only resources <br>- Enforce business-unit data boundaries | Enforces data-governance policies and prevents unauthorized access to protected records. |
| Toggle or Add Validation Flags | configureDefaultValidations |
Enables, disables, or extends built-in validation flags for retrieve requests according to organization-specific policies. | - Require record-existence validation <br>- Enable stricter identifier validation <br>- Disable checks that do not apply to trusted internal channels <br>- Apply different validation policies by tenant or environment | Allows each organization to relax or tighten retrieval checks without modifying the shared implementation. |
| Add Custom Fields or Server-Side Filters | customiseGraphQLQuery |
Modifies the GraphQL query before execution by adding custom fields, relationships, filters, or server-side constraints. | - Add organization-specific Contact fields <br>- Include entitlement or customer-hierarchy data <br>- Apply regional, tenant, or lifecycle-status filters <br>- Request a reduced field set for mobile clients | Returns richer, client-specific data without changing the base schema or introducing additional API versions. |
| Response Enrichment | handlePostOperation |
Processes the query result before it is returned to the client. It can add calculated values, derived fields, audit metadata, or information from related systems. | - Add a calculated customer health score <br>- Include retrieval audit metadata <br>- Flag preferred or high-risk customers <br>- Add aggregated financial, service, or usage insights | Delivers derived fields and audit metadata while preserving the underlying API schema. |
transformRequest
This hook transforms the request context before processing any operation. It is invoked early in the request lifecycle, allowing implementers to adjust the context for subsequent extensibility hooks.
Hook Method
Map<String, Object> transformRequest(Map<String, Object> context)
Sample Apex Implementation
Resolve an external ID to a Salesforce Contact ID.
global class OrganizationGetTransformRequestExt implements comms_apex_ext.IOrganizationPartyManagementGET {
global static Map<String, Object> transformRequest(Map<String, Object> context) {
if (context == null) {
return context;
}
Object idObj = context.get('id');
if (!(idObj instanceof String)) {
return context;
}
String idValue = (String) idObj;
// A 15- or 18-character value already looks like a Salesforce Id; leave it as-is.
if (idValue.length() == 15 || idValue.length() == 18) {
return context;
}
// Otherwise treat it as an external key and resolve it to an Account Id.
List<Account> accounts = [
SELECT Id FROM Account WHERE External_Id__c = :idValue LIMIT 1
];
if (!accounts.isEmpty()) {
context.put('id', accounts[0].Id);
}
return context;
}
// ... other hooks return null / empty map ...
}
configureDefaultValidations
This hook enables, disables, or modifies the built-in validation rules before they execute.
Hook Method
Map<String, Boolean> configureDefaultValidations(Map<String, Boolean> defaultValidationConfiguration, Map<String, Object> context)
Sample Apex Implementation
Add a custom flag consumed by applyCustomValidations.
global class OrganizationGetConfigValidationsExt implements comms_apex_ext.IOrganizationPartyManagementGET {
global static Map<String, Boolean> configureDefaultValidations(
Map<String, Boolean> defaultValidationConfiguration,
Map<String, Object> context
) {
Map<String, Boolean> overrides = defaultValidationConfiguration != null
? defaultValidationConfiguration.clone()
: new Map<String, Boolean>();
overrides.put('segmentAccessCheck', true); // your own flag, read in applyCustomValidations
return overrides;
}
// ... other hooks return null / empty map ...
}
applyCustomValidations
This hook validates custom business logic before retrieving customer record. If validation fails, it rejects the request and returns an error response.
The handler processes return values as follows.
Success:
- Return a map with validationStatus set to "pass" (case-insensitive).
- The API request continues normally.
- Other fields in the map are logged but not used.
Failure:
- Return a map with validationStatus set to "fail" (case-insensitive).
- The API request is terminated immediately.
- Raise a ValidationException containing
- message: value from validationMessage key (or "Validation failed" by default)
- details: value from validationDetails key (optional)
- The client receives an HTTP 400 error response.
Hook Method
Map<String, Object> applyCustomValidations(Map<String, Object> context)
Sample Apex Implementation
Role-based/regional access control.
global class OrganizationGetRbacExt implements comms_apex_ext.IOrganizationPartyManagementGET {
global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {
String organizationId = (String) context.get('id');
Map<String, Object> result = new Map<String, Object>();
try {
User currentUser = [SELECT Id, Profile.Name FROM User WHERE Id = :UserInfo.getUserId() LIMIT 1];
if ('Customer Service Rep'.equals(currentUser.Profile.Name) && organizationId != null) {
Account a = [SELECT Id, BillingCountry FROM Account WHERE Id = :organizationId LIMIT 1];
if (!'US'.equals(a.BillingCountry)) {
result.put('validationStatus', 'fail');
result.put('validationMessage', 'User does not have access to organizations in this region');
result.put('validationDetails', new Map<String, Object>{
'statusCode' => 'BAD_REQUEST',
'errorCode' => 'REGION_RESTRICTION'
});
}
}
} catch (Exception e) {
result.put('validationStatus', 'fail');
result.put('validationMessage', 'Validation error: ' + e.getMessage());
}
return result; // empty => proceed; validationStatus="fail" => HTTP 400
}
// ... other hooks return null / empty map ...
}
customiseGraphQLQuery
This hook modifies the GraphQL query before execution to add fields, filters, or transformations.
Hook Method
Map<String, Object> customiseGraphQLQuery(Map<String, Object> graphQLAsMap, Map<String, Object> context)
Sample Apex Implementation
Add custom fields and a filter: For the Organization GET, target the organizationData, which represents the Account node.
global class OrganizationGetCustomGraphQLExt implements comms_apex_ext.IOrganizationPartyManagementGET {
global static Map<String, Object> customiseGraphQLQuery(
Map<String, Object> graphQLAsMap,
Map<String, Object> context
) {
List<Map<String, Object>> nodes = new List<Map<String, Object>>();
// Add custom Account fields.
nodes.add(new Map<String, Object>{
'path' => 'organizationData',
'addFields' => new List<String>{ 'Industry', 'AnnualRevenue' }
});
// Only return active accounts (append to any existing WHERE clause).
nodes.add(new Map<String, Object>{
'path' => 'organizationData',
'addFilters' => new Map<String, Object>{
'clearExistingFilters' => false,
'filterGraphQlSnippet' => '{ Active__c: { eq: true } }'
}
});
return new Map<String, Object>{ 'nodes' => nodes };
}
// ... other hooks return null / empty map ...
}
handlePostOperation
This hook post-processes and transforms the API response after data retrieval.
Hook Method
Map<String, Object> handlePostOperation(Map<String, Object> graphQLQueryResultAsMap, Map<String, Object> constructedTMFResponse, Map<String, Object> context)
Sample Apex Implementation
Enrich the response in place.
global class OrganizationGetEnrichExt implements comms_apex_ext.IOrganizationPartyManagementGET {
global static Map<String, Object> handlePostOperation(
Map<String, Object> graphQLQueryResultAsMap,
Map<String, Object> constructedTMFResponse,
Map<String, Object> context
) {
if (constructedTMFResponse != null) {
constructedTMFResponse.put('retrievedBy', UserInfo.getName());
constructedTMFResponse.put('lastRetrievedAt',
DateTime.now().format('yyyy-MM-dd\'T\'HH:mm:ss\'Z\''));
}
// Returning null keeps the mutated constructedTMFResponse as the final payload.
return null;
}
// ... other hooks return null / empty map ...
}
Full Implementation Example
Here's a complete sample Apex implementation using all supported hooks.
/**
* Complete TMF632 Organization (Party Management) GET API extension,
* demonstrating all five lifecycle hooks with business logic.
*/
global class OrganizationPartyManagementGETExtension implements comms_apex_ext.IOrganizationPartyManagementGET {
// Hook 1: normalize / resolve the request context.
global static Map<String, Object> transformRequest(Map<String, Object> context) {
if (context != null) {
context.put('transformedBy', 'apex');
}
return context;
}
// Hook 2: keep default validations unchanged (GET has none).
global static Map<String, Boolean> configureDefaultValidations(
Map<String, Boolean> defaultValidationConfiguration,
Map<String, Object> context
) {
return new Map<String, Boolean>();
}
// Hook 3: block requests for disallowed fields.
global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {
if (context != null && context.get('fields') instanceof List<Object>) {
List<Object> fields = (List<Object>) context.get('fields');
if (fields != null && fields.contains('invalidField')) {
return new Map<String, Object>{
'validationStatus' => 'fail',
'validationMessage' => 'Invalid field requested'
};
}
}
return new Map<String, Object>();
}
// Hook 4: add custom fields + an active-only filter.
global static Map<String, Object> customiseGraphQLQuery(
Map<String, Object> graphQLAsMap,
Map<String, Object> context
) {
List<Map<String, Object>> nodes = new List<Map<String, Object>>();
nodes.add(new Map<String, Object>{
'path' => 'organizationData',
'addFields' => new List<String>{ 'Industry', 'AnnualRevenue' }
});
nodes.add(new Map<String, Object>{
'path' => 'organizationData',
'addFilters' => new Map<String, Object>{
'clearExistingFilters' => false,
'filterGraphQlSnippet' => '{ Active__c: { eq: true } }'
}
});
return new Map<String, Object>{ 'nodes' => nodes };
}
// Hook 5: enrich the response in place.
global static Map<String, Object> handlePostOperation(
Map<String, Object> graphQLQueryResultAsMap,
Map<String, Object> constructedTMFResponse,
Map<String, Object> context
) {
if (constructedTMFResponse != null) {
constructedTMFResponse.put('retrievedBy', UserInfo.getName());
}
return null;
}
}
Response Structure
Single Organization Response (GraphQL)
{
"id": "001xx000003DHP",
"href": "/connect/comms/partymanagement/v5/organization/001xx000003DHP",
"@type": "Organization",
"@baseType": "Party",
"name": "Acme Corporation",
"organizationType": "Customer",
"contactMedium": [
{
"@type": "GeographicAddressContactMedium",
"street1": "1 Market St",
"city": "San Francisco",
"stateOrProvince": "CA",
"postCode": "94105",
"country": "US"
},
{ "@type": "PhoneContactMedium", "phoneNumber": "+1-555-0100" },
{ "@type": "FaxContactMedium", "faxNumber": "+1-555-0101" }
],
"relatedParty": [
{
"@type": "RelatedPartyRefOrPartyRoleRef",
"role": "Decision Maker",
"partyOrPartyRole": {
"id": "003xx000004MZZ",
"href": "/connect/comms/partymanagement/v5/individual/003xx000004MZZ",
"name": "John Smith",
"@type": "PartyRef",
"@referredType": "Individual"
}
},
{
"@type": "RelatedPartyRefOrPartyRoleRef",
"role": "Supplier",
"partyOrPartyRole": {
"id": "001xx000003DaB",
"href": "/connect/comms/partymanagement/v5/organization/001xx000003DaB",
"name": "Globex Supplies",
"@type": "PartyRef",
"@referredType": "Organization"
}
}
],
"organizationParentRelationship": {
"@type": "OrganizationParentRelationship",
"organization": {
"id": "001xx000003DEF",
"href": "/connect/comms/partymanagement/v5/organization/001xx000003DEF",
"name": "Acme Holdings",
"@type": "OrganizationRef"
}
},
"organizationChildRelationship": [
{
"@type": "OrganizationChildRelationship",
"organization": {
"id": "001xx000003DXY",
"href": "/connect/comms/partymanagement/v5/organization/001xx000003DXY",
"name": "Acme West",
"@type": "OrganizationRef"
}
}
]
}
List Response (GraphQL)
Default (without pageInfo) — a bare JSON array of Organization objects:
[
{
"id": "001xx000003DHP",
"href": "/connect/comms/partymanagement/v5/organization/001xx000003DHP",
"@type": "Organization",
"@baseType": "Party",
"name": "Acme Corporation",
"organizationType": "Customer"
},
{
"id": "001xx000003DaB",
"href": "/connect/comms/partymanagement/v5/organization/001xx000003DaB",
"@type": "Organization",
"@baseType": "Party",
"name": "Globex Supplies",
"organizationType": "Partner"
}
]
GraphQL Query Transformations
Query transformations provide the ability to enhance GraphQL queries by adding fields, applying filters, or modifying the sort order, while preserving the original query definition.
Query Enhancements: Fields and Filters
Original Query (GraphQL):
query {
myAccounts: accounts {
id
name
}
}
Transformation Instructions (JSON):
{
"nodes": [
{
"path": "myAccounts",
"addFields": ["email", "phone"],
"addFilters": {
"clearExistingFilters": true,
"filterGraphQlSnippet": "{ status: { eq: \"ACTIVE\" } }"
}
}
]
}
Result (GraphQL):
query {
myAccounts: accounts(where: { status: { eq: "ACTIVE" } }) {
id
name
email
phone
}
}
Query Enhancements: Fields, Filters, and Ordering
Original Query (GraphQL):
query {
myAccounts: accounts {
id
name
}
}
Transformation Instructions (JSON):
{
"nodes": [
{
"path": "myAccounts",
"addFields": ["revenue", "industry"],
"addFilters": {
"clearExistingFilters": true,
"filterGraphQlSnippet": "{
and: [
{ status: { eq: \"ACTIVE\" } },
{ revenue: { gte: 1000000 } }
]
}"
},
"orderBy": {
"clearExistingOrderBy": true,
"orderGraphQlSnippet": "{ revenue: { order: DESC } }"
}
}
]
}
Result (GraphQL):
query {
myAccounts: accounts(
where: {
and: [
{ status: { eq: "ACTIVE" } },
{ revenue: { gte: 1000000 } }
]
},
orderBy: { revenue: { order: DESC } }
) {
id
name
revenue
industry
}
}
Query Enhancements: Multiple Query Paths with Different Transformations
Original Query (GraphQL):
query {
myAccounts: accounts {
id
name
myContacts: contacts {
id
name
}
}
}
Transformation Instructions (JSON):
{
"nodes": [
{
"path": "myAccounts",
"addFields": ["email"],
"addFilters": {
"clearExistingFilters": true,
"filterGraphQlSnippet": "{ status: { eq: \"ACTIVE\" } }"
}
},
{
"path": "myAccounts.myContacts",
"addFields": ["email", "phone"],
"orderBy": {
"clearExistingOrderBy": true,
"orderGraphQlSnippet": "{ name: { order: ASC } }"
}
}
]
}
Result (GraphQL):
query {
myAccounts: accounts(where: { status: { eq: "ACTIVE" } }) {
id
name
email
myContacts: contacts(orderBy: { name: { order: ASC } }) {
id
name
email
phone
}
}
}
Query Enhancements: Fields and Snippets
Original Query (GraphQL):
query {
myAccounts: accounts {
id
name
}
}
Transformation Instructions (JSON):
{
"nodes": [
{
"path": "myAccounts",
"addFields": ["email"]
},
{
"path": "metrics",
"insertGraphQlSnippet": "{
metrics: systemMetrics {
totalCount
activeCount
lastSyncTime
}
}"
}
]
}
Result (GraphQL):
query {
myAccounts: accounts {
id
name
email
}
metrics: systemMetrics {
totalCount
activeCount
lastSyncTime
}
}