Apex workaround for doing callout after DML

If you are trying to do a callout after a DML operation you would have seen this error message:

You have uncommitted work pending. Please commit or rollback before calling out

A way around doing callouts after a DML is to do the DML as its own internal callout. This is possible by creating a internal endpoint which will be called first to insert the records. After the insert is complete serialize the response and use that for the external callout.

Steps to following:
1. Create internal endpoint to insert records
2. Create internal Httprequest to the endpoint above
3. Combine internal callout and external callout

Create internal endpoint to insert records

	global with sharing class PostInternal implements Rest_Dispatcher.Dispatchable {
			private Rest_Global_Request_Json requestBody;

			global String getURIMapping(){
							return Rest_Constants.URI_ACCOUNT + '/internal';
			}

			global void setRequestBody(Rest_Global_Request_Json requestBody){
					this.requestBody = requestBody;
			}

			global Rest_Global_Json execute(Map<String, String> parameters){
					Rest_Global_Json globalJson = new Rest_Global_Json();
					List<Financial_Account__c> financialAccountList = (List<Financial_Account__c>)JSON.deserialize(requestBody.getRequest(), List<Financial_Account__c>.class);
					List<Financial_Account__c> bpFinancialAccounts = App_Service.instance.insertFinancialAccount(financialAccountList);
					globalJson.setResult(Selector_Financial_Account.newInstance().selectByFinAccountAccounts(bpFinancialAccounts).values());
					return globalJson;
			}
}

Create internal Httprequest to the internal endpoint

public static HttpResponse doInternalCalloutToInsertAccount(List<Financial_Account__c> financialAccountList){
		Http h = new Http();
		Httprequest req = new Httprequest();
		req.setMethod('POST');

		req.setHeader('Authorization','Bearer ' + UserInfo.getSessionId());
		req.setHeader('Content-Type','application/json');
		Object finaAccountListObject = (Object)financialAccountList;
		req.setBody(JSON.serialize(finaAccountListObject));
		req.setEndpoint(System.URL.getSalesforceBaseURL().toExternalForm() + '/services/apexrest/v1/account/internal');
		HttpResponse response = h.send(req);
		return response;
}

Combine internal callout and external callout

HttpResponse response = doInternalCalloutToInsertAccount(financialAccountList);

	if (response!=null && response.getBody()!=null){
		Map<String,Object> serializeResponse = (Map<String,Object>)JSON.deserializeUntyped(response.getBody());
		bpAccountInserted = (List<Financial_Account__c>)JSON.deserialize(JSON.serialize(serializeResponse.get('result')), List<Financial_Account__c>.class);
		List<Metadata.BPUpdatedAccounts> bpAccountUpdateList = new List<Metadata.BPUpdatedAccounts>();

		for (Financial_Account__c bpAccount : bpAccountInserted){
			...
		}

		if (!bpAccountUpdateList.isEmpty())
			new Rest_Events(new Rest_Events.Event(UserInfo.getUserId(), Rest_Events.EventType.UPDATE_ACCOUNTS, bpAccountUpdateList, new List<String>{'Active', 'InActive'})).handleEvent();
}

Apex Sorting Objects with Comparable Interface

Sorting objects generic class

public abstract class Comparator {
    public abstract Integer compare(Object o1, Object o2);
    public static void sort(Object[] values, Comparator comp) {
        //  Obtain the list type of values
        Object[] temp = values.clone();
        temp.clear();
        //  Helper class for sorting using Comparable
        Helper[] tempValues = new Helper[0];
        for(Object value: values) {
            tempValues.add(new Helper(comp, value));
        }
        //  Perform sort
        tempValues.sort();
        //  Extract values back into temp list
        for(Helper helper: tempValues) {
            temp.add(helper.value);
        }
        //  And set the list to the new, sorted order
        values.clear();
        values.addAll(temp);
    }
    //  Simply calls Comparator when asked.
    class Helper implements Comparable {
        Comparator method;
        Object value;
        Helper(Comparator comp, Object val) {
            method = comp;
            value = val;
        }
        public Integer compareTo(Object o) {
            return method.compare(value, ((Helper)o).value);
        }
    }
}

Compare Strings

public class AccountNameComparator extends Comparator {
    public override Integer compare(Object a, Object b) {
        return ((Account)a).name.compareTo(((Account)b).name);
    }
}

Compare Dates

public class PriceHistoryPriceDateCompare extends Comparator {
    public override Integer compare(Object a, Object b) {
      return (DateTime.newInstance(((History__c)a).Price_Date__c.year(), ((History__c)a).Price_Date__c.month(),1).getTime().format()).compareTo((DateTime.newInstance(((History__c)b).Price_Date__c.year(), ((History__c)b).Price_Date__c.month(), 1).getTime()).format());
    }
	}

Test Class

@isTest(SeeAllData=true) static void testComparator(){
		List<Account> accountToSort = new List<Account>();
		for (Integer k = 70;k > 64;k--){
			Account acc = (Account)SmartFactory.createSObject('Account');
			acc.Name = String.fromCharArray(new List<integer> {k});
			insert acc;
			accountToSort.add(acc);
		}

		Test.startTest();
    	Comparator.sort(accountToSort, new AccountNameComparator());
			System.assertEquals(accountToSort.get(0).Name, 'A');
			System.assertEquals(accountToSort.get(1).Name, 'B');
			System.assertEquals(accountToSort.get(2).Name, 'C');
			System.assertEquals(accountToSort.get(3).Name, 'D');
			System.assertEquals(accountToSort.get(4).Name, 'E');
			System.assertEquals(accountToSort.get(5).Name, 'F');
		Test.stopTest();
	}

Apex Cache RecordTypes for fast retrieval

Apex class to lookup record types and cache them

  private static Map<Schema.SObjectType,Map<String,Id>> rtypesCache;

static {
   rtypesCache = new Map<Schema.SObjectType,Map<String,Id>>();//convenient map, formatted from r    esults.
}

  public static Map<String, Id> getRecordTypeMapForObjectGeneric(Schema.SObjectType token) {
      Map<String, Id> mapRecordTypes = rtypesCache.get(token);
      if (mapRecordTypes == null) {
          mapRecordTypes = new Map<String, Id>();
          rtypesCache.put(token,mapRecordTypes);
      } else {
           return mapRecordTypes;
      }

      Schema.DescribeSObjectResult obj = token.getDescribe();
      if (results == null || results.isEmpty()) {
          String soql = 'SELECT Id, Name, DeveloperName, sObjectType FROM RecordType WHERE IsActive = TRUE';
          try {
              results = Database.query(soql);
          } catch (Exception ex) {
              results = new List<SObject>();
          }
      }

      Map<Id,Schema.RecordTypeInfo> recordTypeInfos = obj.getRecordTypeInfosByID();
      for (SObject rt : results) {
          if (recordTypeInfos.get(rt.Id) != null) {
              if (recordTypeInfos.get(rt.Id).isAvailable()) {
                  mapRecordTypes.put(String.valueOf(rt.get('DeveloperName')),rt.Id);
              }
              else {
                  System.debug('The record type ' + rt.get('DeveloperName') + ' for object ' + rt.get('sObjectType') + ' is not availiable for the user.');
              }
          }
      }
      return mapRecordTypes;
    }

Test Class

  @isTest(SeeAllData=true) static void testGetRecordTypeMapForObjectGeneric(){
      Test.startTest();
        Map<String, Id> caseRecordTypeId = App_Service.getRecordTypeMapForObjectGeneric(Case.SobjectType);
        System.Assert(caseRecordTypeId.get('Error')!=null);
        System.Assert(caseRecordTypeId.get('Internal')==null);
      Test.stopTest();
    }

Apex Check User Session Still Valid

Checking if a user has a valid session before making a query or call else an INVALID_SESSION error will be returned. The value of time remaining for the session can be cached and referenced so this query only runs a few times. Every time a user makes a request we can check the cache and see if the user has time remaining.

Query session information and returning UserSession model

public static UserSession getUserSessionInfo(User userObj){
      UserSession bpUserSession = new UserSession();
      List<AuthSession> session = [Select LastModifiedDate, NumSecondsValid from AuthSession where UsersId = : userObj.Id] ;
      bpUserSession.setSessionValid(false);

      for(AuthSession sessionObj : session){
          bpUserSession.setSecondsValid(sessionObj.NumSecondsValid);
          bpUserSession.setLastModifiedDate(sessionObj.LastModifiedDate);
      }

      if (bpUserSession.getSecondsValid()!=null)
        bpUserSession.setSessionExpireTime(bpUserSession.getLastModifiedDate().addSeconds(bpUserSession.getSecondsValid()));

      if( bpUserSession.getSessionExpireTime() > System.now())
          bpUserSession.setSessionValid(true);

      List<AggregateResult> loginHistoryObj = [SELECT MAX(LoginTime) FROM LoginHistory WHERE UserId = : userObj.Id GROUP BY UserId];

      DateTime loginDateTime = (DateTime)loginHistoryObj[0].get('expr0');
      bpUserSession.setLoginTime(loginDateTime);
      Date loginDate = loginDatetime.date();
      if( logindate != (DateTime.now()).date())
          bpUserSession.setSessionValid(false);

     return bpUserSession;
    }

User Session model to set session information

    public class UserSession {
      DateTime lastModifiedDate;
      Integer secondsValid;
      DateTime sessionExpireTime;
      Boolean sessionValid;
      DateTime loginTime;

      public void setLastModifiedDate(DateTime lastModifiedDate){
        this.lastModifiedDate = lastModifiedDate;
      }

      public DateTime getLastModifiedDate(){
        return lastModifiedDate;
      }

      public void setSecondsValid(Integer secondsValid){
        this.secondsValid = secondsValid;
      }

      public Integer getSecondsValid(){
        return secondsValid;
      }

      public void setSessionExpireTime(DateTime sessionExpireTime){
        this.sessionExpireTime = sessionExpireTime;
      }

      public DateTime getSessionExpireTime(){
        return sessionExpireTime;
      }

      public void setSessionValid(Boolean sessionValid){
        this.sessionValid = sessionValid;
      }

      public Boolean getSessionValid(){
        return sessionValid;
      }

      public void setLoginTime(DateTime loginTime){
        this.loginTime = loginTime;
      }

      public DateTime getLoginTime(){
        return loginTime;
      }
    }

Apex Logging Using Static Resources

Define the Error messages detail in json format as static resource

[{
	"id": "REQUESTED_OPERATION_NOT_PERMITTED",
	"code": 1,
	"clientTemplate": "the current rest operation is not permitted for this endpoint",
	"systemTemplate": "the current rest operation is not permitted for this endpoint"
}, {
	"id": "REQUESTED_ID_MISSING_OR_INVALID",
	"code": 2,
	"clientTemplate": "id is missing or invalid for the specific request",
	"systemTemplate": "id is missing or invalid for the specific request Id {0}"
}, {
	"id": "URL_INVALID",
	"code": 3,
	"clientTemplate": "url invalid and does not exist",
	"systemTemplate": "url invalid and does not exist UserId: {0} | Type: {1} | Path: {2} | Body: {3}"
}, {
	"id": "INPUT_JSON_INVALID",
	"code": 4,
	"clientTemplate": "the input json provided is invalid",
	"systemTemplate": "the input json provided is invalid"
}]

Define a ResponseCode class to parse the json into a List of ResponseCodes

global with sharing class ResponseCode {

  public String id { get; set; }
  public Integer code { get; set; }
  public String clientTemplate { private get; set { clientTemplate = value; } }
  public String systemTemplate { private get; set { systemTemplate = value; } }

  public String formattedClientMessage { get; set; }
  public String formattedSystemMessage { get; set; }

  global void formatMessages(List<String> args, Exception ex) {
    if (args != null && args.size() != 0) {
      formattedClientMessage = String.format(clientTemplate, args);
      if (systemTemplate != null) {
        formattedSystemMessage = String.format(systemTemplate, args);
      }
    } else {
      formattedClientMessage = clientTemplate;
      formattedSystemMessage = systemTemplate;
    }
    if (ex != null) {
      if (formattedSystemMessage == null) {
        formattedSystemMessage = formattedClientMessage;
      }
      formattedSystemMessage += ' -> Cause by: ' + ex.getTypeName() + ' - ' + ex.getMessage() + '. Cause trace: ' + ex.getStackTraceString();
    }
  }

  global ResponseCode copy() {
    ResponseCode theNewObj = new ResponseCode();
    theNewObj.id = this.id;
    theNewObj.code = this.code;
    theNewObj.clientTemplate = this.clientTemplate;
    theNewObj.systemTemplate = this.systemTemplate;
    return theNewObj;
  }

	global ResponseCode(){
	}

  global override String toString() {
    return 'ResponseCode(id=' + id + ';code=' + code + ';clientTemplate=' + clientTemplate + ';systemTemplate=' + systemTemplate + ';formattedClientMessage=' + formattedClientMessage + ';formattedSystemMessage=' + formattedSystemMessage + ')';
  }
}

Define a ResponseCode Manager to translate the Codes into a map to get by id

public with sharing class ResponseCodes_Mgr{

	private static Map<String, ResponseCode> itsResponseCodes = new Map<String, ResponseCode>();

	static {
    try {
      StaticResource theCodesSrc = [SELECT Id, Body FROM StaticResource WHERE Name = 'ResponseCode' LIMIT 1];
      if (theCodesSrc != null) {
    		String theCodesJSON = theCodesSrc.Body.toString();
        List<ResponseCode> theCodesList = (List<ResponseCode>) JSON.deserialize(theCodesJSON, List<ResponseCode>.class);
        for (ResponseCode theCode: theCodesList) {
          itsResponseCodes.put(theCode.id, theCode);
        }
        System.debug('Loaded ' + itsResponseCodes.size() + ' response codes into static map');
      } else {
        System.debug(LoggingLevel.ERROR, 'Cannot query ResponseCode static resource from DB');
      }
    } catch (Exception ex) {
      System.debug(LoggingLevel.ERROR, 'ERROR loading response codes: ' + ex.getMessage());
    }

	}

  public static ResponseCode getCode(String anID) {
    return getCode(anID, null, null);
  }

  public static ResponseCode getCode(String anID, Exception anExp) {
    return getCode(anID, anExp, null);
  }

  public static ResponseCode getCode(String anID, List<String> args) {
    return getCode(anID, null, args);
  }

  public static ResponseCode getCode(String anID, Exception anExp, List<String> args) {
    // make a copy so that the caller can do whatever with the object
    // multiple callers might use same ID with different args list
    ResponseCode theCode = itsResponseCodes.get(anID);
    if (theCode == null) {
      theCode = itsResponseCodes.get('UNEXPECTED_RESPONSE_CODE').copy();
      List<String> theArgs = new List<String>();
      theArgs.add(anID);
      if (args != null) {
        theArgs.add(args + '');
      }
      theCode.formatMessages(theArgs, anExp);
      System.debug(LoggingLevel.ERROR, 'Unknown response code ' + anID + '. Returning UNEXPECTED_RESPONSE_CODE and ignoring args list ' + args);
    } else {
      theCode = theCode.copy();
      theCode.formatMessages(args, anExp);
    }
    return theCode;
  }
}

Catch exceptions and log exceptions to logging provide

}catch (JSONException jsonEx){
  responseCode = ResponseCodes_Mgr.getCode('JSON_SERIALIZATION_FAILED', jsonEx, new List<String>{UserInfo.getUserId(), requestHeaders.get(TRACE_ID_KEY), requestHeaders.get('timestamp'), requestType.name(), request.requestURI, SHOW_BODY && request.requestBody!= null ? request.requestBody.toString() : ''});
  json.setResponseCode(responseCode);
} catch (SearchException searchEx){
  responseCode = ResponseCodes_Mgr.getCode('SEARCH_FAILED', searchEx, new List<String>{UserInfo.getUserId(), requestHeaders.get(TRACE_ID_KEY), requestHeaders.get('timestamp'), requestType.name(), request.requestURI, SHOW_BODY && request.requestBody!= null ? request.requestBody.toString() : ''});
  json.setResponseCode(responseCode);
} catch (CalloutException calloutEx){
  responseCode = ResponseCodes_Mgr.getCode('CALLOUT_FAILED', calloutEx, new List<String>{UserInfo.getUserId(), requestHeaders.get(TRACE_ID_KEY), requestHeaders.get('timestamp'), requestType.name(), request.requestURI, SHOW_BODY && request.requestBody!= null ? request.requestBody.toString() : ''});
  json.setResponseCode(responseCode);  
} catch (DmlException dmlEx){
  responseCode = ResponseCodes_Mgr.getCode('DATABASE_OPERATION_FAILED', dmlEx, new List<String>{UserInfo.getUserId(), requestHeaders.get(TRACE_ID_KEY), requestHeaders.get('timestamp'), requestType.name(), request.requestURI, SHOW_BODY && request.requestBody!= null ? request.requestBody.toString() : ''});
  json.setResponseCode(responseCode);
} catch (SObjectException sobjectEx){
  responseCode = ResponseCodes_Mgr.getCode('SOBJECT_OPERATION_FAILED', sobjectEx, new List<String>{UserInfo.getUserId(), requestHeaders.get(TRACE_ID_KEY), requestHeaders.get('timestamp'), requestType.name(), request.requestURI, SHOW_BODY && request.requestBody!= null ? request.requestBody.toString() : ''});
  json.setResponseCode(responseCode);
  String serializeJson = Rest_Global_Json.instance.serialize(json);
  res.responseBody = Blob.valueof(serializeJson);
} catch (QueryException queryEx){
  responseCode = ResponseCodes_Mgr.getCode('QUERY_OPERATION_FAILED', queryEx, new List<String>{UserInfo.getUserId(), requestHeaders.get(TRACE_ID_KEY), requestHeaders.get('timestamp'), requestType.name(), request.requestURI, SHOW_BODY && request.requestBody!= null ? request.requestBody.toString() : ''});
  json.setResponseCode(responseCode);
} catch (Exception ex){
  responseCode = ResponseCodes_Mgr.getCode('REST_DISPATCHER_FAILED', ex, new List<String>{UserInfo.getUserId(), requestHeaders.get(TRACE_ID_KEY), request.headers.get('timestamp'), requestType.name(), request.requestURI, SHOW_BODY && request.requestBody!= null ? request.requestBody.toString() : ''});
  json.setResponseCode(responseCode);
}

Apex HttpCalloutMock DML workaround

When doing a callout it has to happen before the DML action. If it happens after the DML it will throw an error or will not execute. Workaround I used is to manually keep track of the mock response, and call the respond method to intercept Http.send when running a test. By checking for Test.isRunningTest() && (mock!=null) we can send the mock response.

Checking for test running and returning mock response

	public static HttpCalloutMock mock = null;
	public HttpResponse callApiEndpoint(String apiEndpoint, String method, Object aPayload) {
		HttpRequest req = new HttpRequest();
    try {
      if (apiEndpoint != null) {
        req.setTimeout(120000);
        req.setMethod(method);
        setAuthHeader(req);
        req.setEndpoint(WebCustomSettings.Heroku_API_URL__c + apiEndpoint);
				if (aPayload!=null)
        	req.setBody(String.valueOf(aPayload));
        System.debug('Sending api request to endpoint' + apiEndpoint);
				if (Test.isRunningTest() && (mock!=null)) {
					 return mock.respond(req);
			 	} else {
					Http http = new Http();
	        return http.send(req);
				}

      } else {
        System.debug('Service apiEndpoint and payload must not be null');
        throw new Rest_Exception(ResponseCodes_Mgr.getCode('HEROKU_REQUEST_CALLOUT_FAILED'));
      }
    } catch (Exception ex) {
      System.debug('ERROR sending sync request to Heroku: ' + ex);
      List<String> theArgs = new List<String>();
      theArgs.add('Api');
      theArgs.add(req.getEndpoint());
      throw new Rest_Exception(ResponseCodes_Mgr.getCode('HEROKU_REQUEST_CALLOUT_FAILED', ex, theArgs));
    }
  }

Test class

  Test.startTest();
        System.runAs(new User(Id=userWithAccountContact.get('UserId'))){
          //Setting the mock variable and it's also a test so will return the mock response
          Heroku_Services_Api.mock = new Heroku_Services_Api_Mock(200, 'Complete', '{}',null);
          req.requestBody = Blob.valueOf('{"id":"'+ account.Id +'", "clientId":"' + userWithAccountContact.get('ContactId') + '", "amount":122233,"endDate":"2022-01-01"}');
          req.requestURI = '/v1/account';
          req.httpMethod = 'PATCH';
          RestContext.request = req;
          RestContext.response = res;

          try{
            Rest_Dispatcher.doPatch();
          } catch(Rest_Exception ex){
            System.assertEquals(ex.itsRespCode.formattedSystemMessage, 'blah');
          }

          System.assert(res.responseBody!=null);
          System.assertEquals(res.statusCode, 200);
      }
      Test.stopTest();

Apex Dynamic Object Mapping for REST endpoints

Writing rest endpoints in Apex can be hard and exposing Salesforce fields to external application may not make allot of sense as custom fields end with __c and some fields are not writable or systems fields. I have worked on creating a Serializer that could serialize a Salesforce object mapping and translate that back to a Salesforce SObject.

Let say you can provide an endpoint to an app that could return the metadata of that object as well as the list of fields it expects. The mapping shows a map of keys and values. The keys are the Salesforce SObject fields and the values are the fields exposed to external application.

{
  "serializeNulls": true,
  "mapKeysFlag": true,
  "mapKeys": {
    "responseCode": "responseCode",
    "result": "result",
    "userId": "userId",
    "size": "size",
    "status": "status"
  },
  "userId": "00536000000erTFAAY",
  "result": {
    "external_id__c": "externalid",
    "sicdesc": "sicdesc",
    "accountsource": "accountsource",
    "site": "site",
    "rating": "rating",
    "description": "description",
    "tickersymbol": "tickersymbol",
    "ownership": "ownership",
    "numberofemployees": "numberofemployees",
    "billingcountry": "billingcountry",
    "billingpostalcode": "billingpostalcode",
    "billingstate": "billingstate",
    "billingcity": "billingcity",
    "billingstreet": "billingstreet",
    "parentid": "parentid",
    "type": "type",
    "name": "name",
    "id": "id"
  }
}

An application can send us data in the following way and we can serialize it to the Account sObject without doing any mapping.

{"externalid":"2333", "photourl": "http://blah.com", "website": "http://web.com", "accountnumber": "3243232", "fax": "2343432", "phone": "32222"}

Define a serializer to swap the key from application with the fields of the Salesforce object

public abstract class Rest_Serializer {

		public Rest_Serializer(){}

		private Map<String,String> mapKeys;
		private boolean serializeNulls = true;
		private boolean mapKeysFlag = true;

		public Rest_Serializer(Map<String,String> mapping){
			this.mapKeys = mapping;
		}

		public void setSerializeNulls(Boolean serializeNulls){
			this.serializeNulls = serializeNulls;
		}

		public void setMapKeysFlag(Boolean mapKeysFlag){
			this.mapKeysFlag = mapKeysFlag;
		}

		public String serialize(Object obj){
			String retString = JSON.serialize(obj);
			if (retString.length() < 50000){
				retString = transformStringForSerilization(retString);
				if(serializeNulls){
					retString = removeNullsFromJSON(retString);
				}
			} else {
		//throw exception
			}
			return retString;
		}

		public Object deserialize(String jsonString, System.Type type){
			jsonString = transformStringForDeserilization(jsonString);
			Object obj = JSON.deserialize(jsonString, type);
			return obj;
		}

		private String transformStringForSerilization(String s){
			if (mapKeys!=null && mapKeysFlag){
				return replaceAll(s, mapKeys);
			}
			return s;
		}

		private String transformStringForDeserilization(String s){
			Map<String,String> flippedMap = new Map<String,String>();
			if (mapKeys!=null && mapKeysFlag){
				for(String key : mapKeys.keySet()){
					flippedMap.put(mapKeys.get(key), key);
				}
			}
			return replaceAll(s, flippedMap);
		}

		private String removeNullsFromJSON(String s){
				return s.replaceAll('("[\\w]*":null,)|(,?"[\\w]*":null)','');
		}

		private String replaceAll(String s, Map<String,String> toFromMap){
			if (mapKeys!=null && mapKeysFlag){
				for(String key : toFromMap.keySet()){
						s = s.replaceAll('"'+key+'":', '"'+toFromMap.get(key)+'":');
				}
			}
			return s;
		}

		public Boolean getSerializeNulls(){
			return serializeNulls;
		}
}

Extend the Rest Serializer to create a DynamicModel that will take all fields and serialize them to an SObject

public class DynamicModel extends Rest_Serializer {
    public DynamicModel(Map<String,String> fieldMapping){
       super(fieldMapping);
    }
}

Get model name and return SObject Fieldsfields

	public static Map<String,String> getModelByObject(String sObjectName){
		Map<String,String> replaceFieldNames = new Map<String,String>();
		SObjectType accountType = Schema.getGlobalDescribe().get(sObjectName);
		Map<String,Schema.SObjectField> mfields = accountType.getDescribe().fields.getMap();
		for (String mfield : mfields.keySet()){
			//String uField = mfield.replace('[__c,_]','');
			replaceFieldNames.put(mfield, Rest_Util.toCamelCase(mfield));
		}
		return replaceFieldNames;
	}

	public static Map<String, Schema.DescribeFieldResult> getFieldMetaData(
	  Schema.DescribeSObjectResult dsor, Set<String> fields) {
		Map<String,Schema.DescribeFieldResult> finalMap = new Map<String, Schema.DescribeFieldResult>();

	  Map<String, Schema.SObjectField> objectFields = dsor.fields.getMap();

	  for(String field : fields){
	    if (objectFields.containsKey(field)) {
	      Schema.DescribeFieldResult dr = objectFields.get(field).getDescribe();
	      finalMap.put(field, dr);
	    }
	  }
	  return finalMap;
	}

Get SObject fields for an object and translate application fields to SObject fields and return object

    global with sharing class GetModel implements Rest_Dispatcher.Dispatchable {
			private Rest_Global_Request_Json requestBody;

			global String getURIMapping(){
					return Rest_Constants.URI_SETTINGS + '/model/{objectName}';
			}

			global void setRequestBody(Rest_Global_Request_Json requestBody){
					this.requestBody = requestBody;
			}

			global Rest_Global_Json execute(Map<String, String> parameters){
					Rest_Global_Json globalJson = new Rest_Global_Json();
					String sObjectName = parameters.get('objectName');
					Map<String,String> uiFieldNamesForObject = getModelByObject(sObjectName);
					BP_JSON.DynamicModel dynamicModel = new BP_JSON.DynamicModel(uiFieldNamesForObject);
					Schema.SObjectType convertType = Schema.getGlobalDescribe().get(sObjectName);
					Type classType = Type.forName(sObjectName);
					Object transformedObject = dynamicModel.deserialize(requestBody.getRequest(), classType);
					globalJson.setResult(transformedObject);
					return globalJson;
			}

			global override String toString(){
				Rest_Endpoint.Path path = new Rest_Endpoint.Path();
				path.setPath(getURIMapping());
				Rest_Endpoint endpoint = new Rest_Endpoint(path);
				return JSON.serialize(endpoint);
			}
	}
}

Apex callout to RabbitMQ Queue

Create AMQP HTTPRequest calling POST /api/exchanges/{username}/{exchangeName}/publish

	public void callAmqpEndpoint(String exchangeName, String method, String aPayload) {
  	HttpRequest req = new HttpRequest();
    try {
      if (exchangeName != null) {
        req.setTimeout(120000);
        req.setMethod(method);
				setAmqpAuthHeader(req);
				System.debug(String.valueOf(aPayload));
        req.setEndpoint(WebCustomSettings.AMQP_Url__c + '/api/exchanges/' + WebCustomSettings.AMQP_Credentials__c.split(':')[0] + '/' + exchangeName + '/publish');
				if (aPayload!=null)
        	req.setBody(aPayload);
        System.debug('Sending api request to endpoint' + req.getEndpoint());
        Http http = new Http();
        http.send(req);
      } else {
        throw new Rest_Exception(ResponseCodes_Mgr.getCode('AMQP_REQUEST_FAILED'));
      }
    } catch (Exception ex) {
      System.debug('Error sending amqp request ' + ex);
      List<String> theArgs = new List<String>();
      theArgs.add('AMQP');
      theArgs.add(req.getEndpoint());
      throw new Rest_Exception(ResponseCodes_Mgr.getCode('AMQP_REQUEST_FAILED', ex, theArgs));
    }
  }

Setup AMQP headers

private void setAmqpAuthHeader(HttpRequest aReq) {
    Blob headerValue = Blob.valueOf(WebCustomSettings.AMQP_Credentials__c);
    String authorizationHeader = 'Basic ' + EncodingUtil.base64Encode(headerValue);
    aReq.setHeader('Authorization', authorizationHeader);
		aReq.setHeader('Content-Type', 'application/json');
		aReq.setHeader('X-AMQP-Tracer', requestJson!=null && requestJson.getTraceId()!=null ? requestJson.getTraceId() : '');
  }

Serialize AMQP Request JSON

	private String serializeAmqpRequests(String payload) {
		JSONGenerator generator = JSON.createGenerator(false);
		generator.writeStartObject();
		generator.writeStringField('routing_key','amqp-events');
		generator.writeFieldName('properties');
		generator.writeStartObject();
		generator.writeEndObject();
		generator.writeStringField('payload', payload);
		generator.writeStringField('payload_encoding', 'string');
		generator.writeEndObject();
		return generator.getAsString();
	}

Callout RabbitMQ

public void sendAmqpRequest(String payload){
		 String amqpPayload = serializeAmqpRequests(payload);
		 callAmqpEndpoint('event-exchange', 'POST', amqpPayload);
	}

Apex Regex Validation Factory

Validation Factory

public with sharing class Regex_Validator {

    public static Boolean isValidEmail(String emailAddress) {
        String emailRegex = '^[a-zA-Z0-9._}{+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}$';
        return String.isBlank(emailAddress) ? false : Pattern.compile(emailRegex).matcher(emailAddress).matches();
    }

    public static Boolean isValidPassword(String password){
        String passwordRegex = '^.*(?=.{8,})(?=.*\\d)(?=.*[a-zA-Z])|(?=.{8,})(?=.*\\d)(?=.*[!@#$%^&])|(?=.{8,})(?=.*[a-zA-Z])(?=.*[!@#$%^&]).*$';
        return String.isBlank(password) ? false : Pattern.compile(passwordRegex).matcher(password).matches();
    }

    public static Boolean isValidDate(String dateString){
        String dateRegex = '^\\d{4}-\\d{2}-\\d{2}$';
        return String.isBlank(dateString) ? false : Pattern.compile(dateRegex).matcher(dateString).matches();
    }

    public static Boolean isValidSSN(String ssn){
        String ssnRegex = '[0-9]{3}[-]?[0-9]{2}[-]?[0-9]{4}';
        return String.isBlank(ssn) ? false : Pattern.compile(ssnRegex).matcher(ssn).matches();
    }

    public static Boolean isValidPhone(String phone){
        String phoneRegex='^([0-9\\(\\)\\/\\+ \\-]*)$';
        return String.isBlank(phone) ? false : Pattern.compile(phoneRegex).matcher(phone).matches();
    }

    public static Boolean isValidZipCode(String zipCode){
        String zipRegex='^\\d{5}(?:[-\\s]?\\d{4})?$';
        return String.isBlank(zipCode) ? false : Pattern.compile(zipRegex).matcher(zipCode).matches();
    }

    public static Boolean isValidNumber(String num) {
         String numberRegex = '^[0-9]\\d*(\\.\\d+)?$';
     return String.isBlank(num) ? false : Pattern.compile(numberRegex).matcher(num).matches();
    }

    public static Boolean isValidSfId(String id){
       String idRegex = '[a-zA-Z0-9]{18}|[a-zA-Z0-9]{15}';
       return String.isBlank(id) ? false : Pattern.compile(idRegex).matcher(id).matches();
    }
}

Validation Factory Test Class

@isTest
private class Regex_Validator_Test {

    public static User setupUser(){
        User user = (User)SmartFactory.createSObject('User');
        return user;
    }

    @isTest static void test_isValidEmail() {
        test.startTest();
            System.assertEquals(Regex_Validator.isValidEmail('test@mail.com'),true);
            System.assertEquals(Regex_Validator.isValidEmail('tmichels+5@mygmail.com'),true);
            System.assertEquals(Regex_Validator.isValidEmail('testmail.com'),false);
            System.assertEquals(Regex_Validator.isValidEmail('tes13_?//@tmail.com'),false);
            System.assertEquals(Regex_Validator.isValidEmail('testmail'),false);
            System.assertEquals(Regex_Validator.isValidEmail('!@#$%%^&*()'),false);
            System.assertEquals(Regex_Validator.isValidEmail(null),false);
        test.stopTest();
    }

    @isTest static void test_isValidPassword() {
        test.startTest();
            System.assertEquals(Regex_Validator.isValidPassword('Password123!'),true);
            System.assertEquals(Regex_Validator.isValidPassword('password123!'),true);
            System.assertEquals(Regex_Validator.isValidPassword('password!'),true);
            System.assertEquals(Regex_Validator.isValidPassword('Password123'),false);
            System.assertEquals(Regex_Validator.isValidPassword('blah'),false);
            System.assertEquals(Regex_Validator.isValidPassword('!@#$%%^&*()'),false);
            System.assertEquals(Regex_Validator.isValidPassword('!@#$%%^&*()'),false);
            System.assertEquals(Regex_Validator.isValidPassword(null),false);
      test.stopTest();
    }

    @isTest static void test_isValidDate() {
        test.startTest();
                System.assertEquals(Regex_Validator.isValidDate('01/01/2016'),false);
                System.assertEquals(Regex_Validator.isValidDate('1/1/2016'),false);
                System.assertEquals(Regex_Validator.isValidDate('01-01-2016'),false);
                System.assertEquals(Regex_Validator.isValidDate('2016-01-01'),true);
                System.assertEquals(Regex_Validator.isValidDate('01.01.2016'),false);
                System.assertEquals(Regex_Validator.isValidDate('1.1.2016'),false);
                System.assertEquals(Regex_Validator.isValidDate('33/01/2016'),false);
                System.assertEquals(Regex_Validator.isValidDate('01/32/2016'),false);
                System.assertEquals(Regex_Validator.isValidDate('01/01/3016'),false);
                System.assertEquals(Regex_Validator.isValidDate('adasdsad'),false);
                System.assertEquals(Regex_Validator.isValidDate('!@#$%%^&*()'),false);
                System.assertEquals(Regex_Validator.isValidDate(null),false);
        test.stopTest();
    }

    @isTest static void test_isValidSSN() {
        test.startTest();
            System.assertEquals(Regex_Validator.isValidSSN('222-22-2222'),true);
            System.assertEquals(Regex_Validator.isValidSSN('222-2-2222'),false);
            System.assertEquals(Regex_Validator.isValidSSN('22-22-2222'),false);
            System.assertEquals(Regex_Validator.isValidSSN('22-22-222'),false);
            System.assertEquals(Regex_Validator.isValidSSN('222222222'),true);
            System.assertEquals(Regex_Validator.isValidSSN('2222222'),false);
            System.assertEquals(Regex_Validator.isValidSSN('asdsad'),false);
            System.assertEquals(Regex_Validator.isValidSSN('!@#$%%^&*()'),false);
            System.assertEquals(Regex_Validator.isValidSSN(null),false);
        test.stopTest();
    }

    @isTest static void test_isValidPhone() {
        test.startTest();
                System.assertEquals(Regex_Validator.isValidPhone('(408)421-3722'),true);
                System.assertEquals(Regex_Validator.isValidPhone('4084213722'),true);
                System.assertEquals(Regex_Validator.isValidPhone('+1(408)421-3722'),true);
                System.assertEquals(Regex_Validator.isValidPhone('(333)222-999'),true);
                System.assertEquals(Regex_Validator.isValidPhone('3332229999'),true);
                System.assertEquals(Regex_Validator.isValidPhone('asdsadsad'),false);
                System.assertEquals(Regex_Validator.isValidPhone('!@#$$%%@'),false);
                System.assertEquals(Regex_Validator.isValidPhone(null),false);
        test.stopTest();
    }

    @isTest static void test_isValidZipCode() {
        test.startTest();
            System.assertEquals(Regex_Validator.isValidZipCode('95070'),true);
            System.assertEquals(Regex_Validator.isValidZipCode('95070-2344'),true);
            System.assertEquals(Regex_Validator.isValidZipCode('95070-234'),false);
            System.assertEquals(Regex_Validator.isValidZipCode('950702344'),true);
            System.assertEquals(Regex_Validator.isValidZipCode('99999'),true);
            System.assertEquals(Regex_Validator.isValidZipCode('999'),false);
            System.assertEquals(Regex_Validator.isValidZipCode('9a9n9'),false);
            System.assertEquals(Regex_Validator.isValidZipCode('asdasd'),false);
            System.assertEquals(Regex_Validator.isValidZipCode('!@#$$%%@'),false);
            System.assertEquals(Regex_Validator.isValidZipCode(null),false);
        test.stopTest();
    }

    @isTest static void test_isValidNumber() {
        test.startTest();
            System.assertEquals(Regex_Validator.isValidNumber('95070'),true);
            System.assertEquals(Regex_Validator.isValidNumber('-99'),false);
            System.assertEquals(Regex_Validator.isValidNumber('asdasd'),false);
            System.assertEquals(Regex_Validator.isValidNumber('!@#$$%%@'),false);
            System.assertEquals(Regex_Validator.isValidNumber(null),false);
        test.stopTest();
    }

    @isTest static void test_isValidSfId(){
      test.startTest();
          System.assertEquals(Regex_Validator.isValidSfId('32423324'),false);
          System.assertEquals(Regex_Validator.isValidSfId('00363000005eIbz'),true);
          System.assertEquals(Regex_Validator.isValidSfId('00363000005eIbzAlz'),true);
          System.assertEquals(Regex_Validator.isValidSfId(null),false);
      test.stopTest();
    }
}

Apex Get Enum from String

Get Enum from String
  public Enum EventType { USER_LOGIN, USER_LOGOUT, USER_COMPLETE_FORM, USER_CLICK }

  static Map<String, EventType> nameToEvenType {
    get {
        Map<String, EventType> eventTypeValues = new Map<String, EventType>();
        if (nameToEvenType == null){
          for (EventType enumValue : EventType.values()){
              eventTypeValues.put(String.valueOf(enumValue).toUpperCase(), enumValue);
          }
        }
        return eventTypeValues;
    }
    private set;
}

public static EventType getEventType(String name){
    return String.isBlank(name) ? null : nameToEvenType.get(name.toUpperCase());
}

Test Class
@IsTest
public static void testNameToEvenType(){
 	Test.startTest();
      System.assertEquals(Events.EventType.USER_LOGIN, Events.getEventType('USER_LOGIN'));
      System.assertEquals(Events.EventType.USER_LOGOUT, Events.getEventType('USER_LOGOUT'));
      System.assertEquals(Events.EventType.USER_COMPLETE_FORM, Events.getEventType('USER_COMPLETE_FORM'));
      System.assertEquals(Events.EventType.USER_CLICK, Events.getEventType('USER_CLICK'));
    Test.stopTest(); 
}
%d bloggers like this: