The IIndividualPartyManagementGET 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
Retrieval Lifecycle Use Cases and Hook Mapping
This table lists common use cases and the hooks required for the GET operation.
| Use Case | Hook(s) | Description | Example Scenarios | Benefit |
|---|---|---|---|---|
| External-ID Resolution | transformRequest |
Transforms legacy, external, or customer-specific identifiers into the canonical request format expected by the API. This allows existing clients to continue using familiar identifiers while keeping the underlying service contract consistent. | - Resolve a legacy customer key to { id }<br>- Translate an account number into an internal resource ID<br>- Normalize identifiers received from partner or migration systems |
Preserves backward compatibility and simplifies integration with legacy or external systems. |
| Role-Based and Regional Access Control | applyCustomValidations |
Applies authorization and data-governance rules before executing an operation. Access can be evaluated using the user's role, region, business unit, channel, or request context. | - Restrict access to customers outside the user's assigned region <br>- Permit only supervisors to access high-value customer records <br>- Block partner channels from retrieving internal-only resources | Strengthens data governance and ensures secure, policy-compliant access across users and regions. |
| Configurable Validation Policies | configureDefaultValidations |
Enables, disables, or adjusts standard validation flags according to organization-specific requirements. Validation behavior can be tightened or relaxed without changing the core API implementation. | - Require additional identifier validation for regulated organizations<br>- Disable a non-applicable validation for trusted internal channels <br>- Enable stricter checks in production while relaxing them in test environments | Provides flexible validation behavior while preserving a reusable and consistent API foundation. |
| Dynamic Query Customization | customiseGraphQLQuery |
Modifies GraphQL queries before execution by adding fields, filters, relationships, or server-side constraints. Query behavior can be tailored to the client, organization, or request context without changing the base schema. | - Add organization-specific custom fields <br>- Automatically apply regional or tenant filters <br>- Include entitlement, hierarchy, or relationship data for portal clients <br>- Request a reduced field set for mobile applications | Supports richer, client-specific data requirements while reducing schema changes and API version proliferation. |
| Response Enrichment | handlePostOperation |
Processes successful operation results before returning them to the client. It can add derived fields, attach audit metadata, transform values, or combine the response with information from internal or external sources. | - Add a calculated customer health score <br>- Include audit timestamps or processing metadata <br>- Flag preferred or high-risk customers <br>- Add aggregated financial, service, or usage insights | Delivers richer and more contextual responses while preserving the integrity of 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 IndividualGetTransformRequestExt implements comms_apex_ext.IIndividualPartyManagementGET {
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 a Contact Id.
List<Contact> contacts = [
SELECT Id FROM Contact WHERE External_Id__c = :idValue LIMIT 1
];
if (!contacts.isEmpty()) {
context.put('id', contacts[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 IndividualGetConfigValidationsExt implements comms_apex_ext.IIndividualPartyManagementGET {
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('regionAccessCheck', 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 IndividualGetRbacExt implements comms_apex_ext.IIndividualPartyManagementGET {
global static Map<String, Object> applyCustomValidations(Map<String, Object> context) {
String individualId = (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) && individualId != null) {
Contact c = [SELECT Id, MailingCountry FROM Contact WHERE Id = :individualId LIMIT 1];
if (!'US'.equals(c.MailingCountry)) {
result.put('validationStatus', 'fail');
result.put('validationMessage', 'User does not have access to individuals 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 Individual GET, target the individualDataalias, which represents the Contact node.
global class IndividualGetCustomGraphQLExt implements comms_apex_ext.IIndividualPartyManagementGET {
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 Contact fields.
nodes.add(new Map<String, Object>{
'path' => 'individualData',
'addFields' => new List<String>{ 'Department', 'Loyalty_Tier__c' }
});
// Only return active contacts (append to any existing WHERE clause).
nodes.add(new Map<String, Object>{
'path' => 'individualData',
'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 IndividualGetEnrichExt implements comms_apex_ext.IIndividualPartyManagementGET {
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;
}
}
Full Implementation Example
Here's a complete sample Apex implementation using all supported hooks.
/**
* Complete TMF632 Individual (Party Management) GET API extension,
* demonstrating all five lifecycle hooks with business logic.
*/
global class IndividualPartyManagementGETExtension implements comms_apex_ext.IIndividualPartyManagementGET {
// 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' => 'individualData',
'addFields' => new List<String>{ 'Department', 'Loyalty_Tier__c' }
});
nodes.add(new Map<String, Object>{
'path' => 'individualData',
'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 Individual Response (GraphQL)
{
"id": "003xx000004MZZ",
"href": "/connect/comms/partymanagement/v5/individual/003xx000004MZZ",
"@type": "Individual",
"@baseType": "Party",
"givenName": "John",
"familyName": "Smith",
"name": "John Smith",
"title": "VP of Operations",
"birthDate": "1980-04-12",
"contactMedium": [
{ "@type": "EmailContactMedium", "emailAddress": "john.smith@acme.com" },
{ "@type": "PhoneContactMedium", "phoneNumber": "+1-555-0100" },
{ "@type": "FaxContactMedium", "faxNumber": "+1-555-0101" },
{
"@type": "GeographicAddressContactMedium",
"street1": "1 Market St",
"city": "San Francisco",
"stateOrProvince": "CA",
"postCode": "94105",
"country": "US"
}
],
"relatedParty": [
{
"@type": "RelatedPartyRefOrPartyRoleRef",
"role": "employer",
"partyOrPartyRole": {
"id": "001xx000003DHP",
"href": "/connect/comms/partymanagement/v5/organization/001xx000003DHP",
"name": "Acme Corporation",
"@type": "PartyRef",
"@referredType": "Organization"
}
}
]
}
List Response (GraphQL)
Default (without pageInfo) — a bare JSON array of Individual objects:
[
{
"id": "003xx000004MZZ",
"href": "/connect/comms/partymanagement/v5/individual/003xx000004MZZ",
"@type": "Individual",
"@baseType": "Party",
"givenName": "John",
"familyName": "Smith",
"name": "John Smith"
},
{
"id": "003xx000004Na0",
"href": "/connect/comms/partymanagement/v5/individual/003xx000004Na0",
"@type": "Individual",
"@baseType": "Party",
"givenName": "Jane",
"familyName": "Doe",
"name": "Jane Doe"
}
]
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
}
}