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

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

Apex Advanced Selector Queries

Sub Queries

public Contact getRelatedDetails(Id userId){
       fflib_QueryFactory qf = new fflib_QueryFactory(Contact.sObjectType);
       qf.selectField('name').selectField('Id').setCondition('Id In (Select ContactId from User where Id=:userId)');
       qf.subselectQuery('Goals__r').selectField('Id');
       qf.subselectQuery('Account_Client__r').selectField('Id').selectField('External_Id__c').selectField('Institution_Account__c').setCondition('Status__c=\'Active\'');
       Contact bpContact = Database.query(qf.toSOQL());
       return bpContact;
     }

Pagination with Ordering

  public List<Goal__c> selectByUserIdAndPageNumber(Id userId, Integer pageNumber){
     Integer calcOffset = pageNumber * 200;
     fflib_QueryFactory goalQueryFactory = newQueryFactory();
     fflib_QueryFactory.Ordering ordering = new fflib_QueryFactory.Ordering(Goal__c.CreatedDate, fflib_QueryFactory.SortOrder.DESCENDING, false);
     goalQueryFactory.addOrdering(ordering);
     List<fflib_QueryFactory.Ordering> orderings = goalQueryFactory.getOrderings();
     orderings.remove(0);
     String queryWithOffSet = goalQueryFactory.setCondition('Client__c in (Select ContactId from User where Id=:userId)').setLimit(maxGoalsPerPage).toSOQL() + ' offset ' + calcOffset;
     return (List<Goal__c>) Database.query(queryWithOffSet);
}

Aggregate Query

public List<AggregateResult> getAggregateDetails(List<Id> securityIds){
    String selectedACLevel = 'Level_1__c';

    String formatQuery = String.format('Select {0} sumOfAllocPercent, {1} from Classification__c where Security__c IN :securityIds Group By {2}', new String[]{'sum(Asset_Percent__c)', selectedACLevel, selectedACLevel});
    List<AggregateResult> aggregateResultForAC= (List<AggregateResult>) Database.query(formatQuery);

    return aggregateResultForAC;
}

Implementing Mocking for Apex Tests

Trying to mock a actual database query can be hard. Using the fflib_Mocks jar you can generator Selector Mocks: More info here:

https://github.com/financialforcedev/fflib-apex-mocks

1. Create interface class that extends fflib_ISObjectSelector to mock the Selector 

2. Create interfacemocks.properties file in root of your project

3. Run java -jar apex-mocks-generator-4.0.0.jar “{path}/src/classes” “{path}/interfacemocks.properties” “fflib_Mocks” “{path}/src/classes” “30.0” to generate mocking files

4. Used mocked selector to write stub selector

5. Run mock generator as maven job

1. Create interface class that extends fflib_ISObjectSelector to mock the Selector 
public interface System_Selector extends fflib_ISObjectSelector {
    List<Object> selectByUserId(Id userId);
}

2. Create interfacemocks.properties file in root of your project

System_Selector=Contact:fflib_SObjectMocks.SObjectSelector
3. Run java -jar apex-mocks-generator-4.0.0.jar "{path}/src/classes" "{path}/interfacemocks.properties" "fflib_Mocks" "{path}/src/classes" "30.0" to generate mocking files
/* Generated by apex-mocks-generator version 4.0.1 */
@isTest
public class Selector_Mocks{
    public class Contact extends fflib_SObjectMocks.SObjectSelector implements System_Selector{
        private fflib_ApexMocks mocks;

        public Contact(fflib_ApexMocks mocks){
            super(mocks);
            this.mocks = mocks;
        }
        
        public List<Object> selectByUserId(Id userId){
            return (List<Object>) mocks.mockNonVoidMethod(this, 'selectByUserId', new List<Type> {System.Type.forName('Id')}, new List<Object> {userId});
        }
    }
}

4. Used mocked selector to write stub selector

  @isTest static void testContactSelector(){
        // Create mocks
  		fflib_ApexMocks mocks = new fflib_ApexMocks();
  		fflib_ISObjectUnitOfWork uowMock = new fflib_SObjectMocks.SObjectUnitOfWork(mocks);

  		System_Selector selectorMock = new System_Mocks.Contact(mocks);

  		// Given
  		mocks.startStubbing();
        List<Contact> testContactList = new List<Contact> {
  			new Contact(
  				Id = fflib_IDGenerator.generate(Contact.SObjectType),
  				FirstName = 'Test FirstName',
  				LastName = 'Test LastName',
  				Email = 'test@mail.com') };

        Set<Id> testContactSet = new Map<Id, Contact>(testContactList).keySet();
        User testUserId = new User(	Id = fflib_IDGenerator.generate(User.SObjectType),
  				FirstName = 'Test FirstName',
  				LastName = 'Test LastName',
  				Email = 'test@mail.com' );
        mocks.when(selectorMock.sObjectType()).thenReturn(Contact.SObjectType);
    		mocks.when(selectorMock.selectByUserId(testUserId.Id)).thenReturn(testContactList);
  		mocks.stopStubbing();

      System_Rest_App.Selector.setMock(selectorMock);

      // When
      new System_Mocks.Contact(mocks).selectByUserId(testUserId.Id);

      // Then
      ((System_Selector)
        mocks.verify(selectorMock)).selectByUserId(testUserId.Id);
    }

5. Run mock generator as maven job – ant generate.mocks

  <target name="generate.mocks">
		<java classname="com.financialforce.apexmocks.ApexMockGenerator">
			<classpath>
				<pathelement location="${basedir}/lib/apex-mocks-generator-4.0.1.jar"/>
			</classpath>
			<arg value="${basedir}/src/classes"/>
			<arg value="${basedir}/interfacemocks.properties"/>
			<arg value="Selector_Mocks"/>
			<arg value="${basedir}/src/classes"/>
		</java>
	</target>

Salesforce Chatter Attaching Files to SObject

There are different ways you can use salesforce to attach a file to an SObject using Chatter.

  • Apex Code
  • Chatter REST Api
    public static void createAttachmentFeed(List<Attachment> attachments){
      for (Attachment attachment : attachments){
        // ContentVersion is how you upload a file!
       ContentVersion version = new ContentVersion();
       version.Title=attachment.Name;
       version.PathOnClient = '\\' + attachment.Name;
       version.VersionData = attachment.Body;
       version.NetWorkId = brightPlanWebCustomSettings.Community_Id__c;
       insert version;

       // After you insert the ContentVersion object, a base 'ContentDocument' is established
       // The ID of the ContentDocument is what you need to attach the file to the Chatter post.
       version = [SELECT ID,ContentDocumentId FROM ContentVersion WHERE ID=:version.id];

       // Form a basic post attached to our own feed.
       ConnectApi.FeedItemInput feedItem = new ConnectApi.FeedItemInput();
       feedItem.subjectId = attachment.ParentId; // This can also be an objectID to post the file to.

       // Now connect the feeditem to our already uploaded file.
       feedItem.capabilities = new ConnectAPI.FeedElementCapabilitiesInput();
       feedItem.capabilities.files = new ConnectAPI.FilesCapabilityInput();
       feedItem.capabilities.files.items = new List<ConnectAPI.FileIdInput>();
       ConnectAPI.FileIdInput attachFile = new ConnectAPI.FileIDInput();

       //**** Here is where we attach the specific file to the post!
       attachFile.id = version.contentDocumentid;
       feedItem.capabilities.files.items.add(attachFile);

       // Execute the posting
       ConnectApi.FeedElement feedElement = ConnectApi.ChatterFeeds.postFeedElement(brightPlanWebCustomSettings.Community_Id__c, feedItem);
      }
    }

Some limitations using this is using Blob for version.VersionData = attachment.Body; will run into Apex String length exceeds maximum: 6000000.

Chatter Rest API

POST https://cs43.salesforce.com/services/data/v35.0/connect/communities/0DB63000000003jGAA/chatter/feed-elements/batch

HEADER

Authorization         Bearer {sessionToken}

Content-Type          multipart/form-data; boundary=a7V4kRcFA8E79pivMuV2tukQ85cmNKeoEgJgq

Format is as follows:

{salesforceOrgUrl}/services/data/v35.0/connect/communities/{salesforceCommunityId}chatter/feed-elements/batch

The subjectId is the SObject Id to which you want to link the file, is this case we use our Case Id.

--a7V4kRcFA8E79pivMuV2tukQ85cmNKeoEgJgq
Content-Type: application/json; charset=UTF-8
Content-Disposition: form-data; name="json"
{
"inputs": [
{
"binaryPartNames": [
"file1"
],
"richInput": {
"subjectId": "50063000003YlSe",
"capabilities": {
"content": {
"title": "file1.pdf"
}
},
"feedElementType": "FeedItem"
}
},
{
"binaryPartNames": [
"file2"
],
"richInput": {
"subjectId": "50063000003YlSe",
"capabilities": {
"content": {
"title": "file2.pdf"
}
},
"feedElementType": "FeedItem"
}
}
]
}
--a7V4kRcFA8E79pivMuV2tukQ85cmNKeoEgJgq
Content-Type: application/octet-stream; charset=ISO-8859-1
Content-Disposition: form-data; name="file1"; filename="file1.pdf"

...contents of file1.pdf...

--a7V4kRcFA8E79pivMuV2tukQ85cmNKeoEgJgq
Content-Disposition: form-data; name="file2"; filename="file2.pdf"
Content-Type: application/octet-stream; charset=ISO-8859-1
...contents of file2.pdf...
--a7V4kRcFA8E79pivMuV2tukQ85cmNKeoEgJgq--

Benefits you can batch upload files without hitting an Apex limits.

Both the Chatter and Apex solutions can be used by Community Users to upload files.

Run Spring Boot Scheduler as Heroku Worker

1. Setup Application
2. Setup Scheduler
3. Add Spring Boot dependency
4. Add Procfile
5. Scale Heroku worker

1. Setup Application

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

/**
 * Created by tmichels on 9/20/15.
 */

@SpringBootApplication
@EnableScheduling
public class Application {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class);
    }
}

2. Setup Scheduler

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Set;

/**
 * Created by tmichels on 9/18/15.
 */

@Component
public class Scheduler {

    private static final Logger logger = LoggerFactory.getLogger(Scheduler.class);

    @Scheduled(cron = "0 0 13 * * *")
    public void buyHighPercentageReturnNotes(){
       System.out.println("Scheduler is running");
    }
}

3. Add Spring Boot dependency

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.6.RELEASE</version>
    </parent>
  <dependencies>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter</artifactId>
      </dependency>
  </dependencies>

4. Add Procfile

worker: java $JAVA_OPTS -jar target/*.jar

5. Scale Heroku worker

heroku ps:scale worker=1

Microsoft Bing Adwords Java Integration

Step 1: Setup Bing BulkServiceManager Configuration

package com.integration.adwords.config;

import com.microsoft.bingads.*;
import com.microsoft.bingads.bulk.BulkServiceManager;
import com.microsoft.bingads.campaignmanagement.ICampaignManagementService;
import com.microsoft.bingads.internal.OAuthService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.net.MalformedURLException;
import java.net.URL;

/**
 * Created by tmichels on 7/31/15.
 */
@Configuration
public class BingAdwordsConfig {

    @Value("${BING_ADS_CLIENTID}")
    private String bingAdsClientId;

    @Value("${BING_ADS_CLIENT_SECRET}")
    private String bingAdsClientSecret;

    @Value("${BING_ADS_ACCOUNTID}")
    private String bingAdsAccountId;

    @Value("${BING_ADS_CUSTOMERID}")
    private String bingAdsCustomerId;

    @Value("${BING_ADS_DEVELOPER_TOKEN}")
    private String bingAdsDeveloperToken;

    @Value("${BING_ADS_USERNAME}")
    private String bingAdsUsername;

    @Value("${BING_ADS_PASSWORD}")
    private String bingAdsPassword;

    @Value("${BING_ADS_REFRESH_TOKEN}")
    private String bingAdsRefreshToken;


    @Bean
    public OAuthDesktopMobileAuthCodeGrant oAuthWebAuthCodeGrant() throws MalformedURLException {
        OAuthDesktopMobileAuthCodeGrant oAuthWebAuthCodeGrant = new OAuthDesktopMobileAuthCodeGrant(bingAdsClientId, bingAdsRefreshToken);
        return oAuthWebAuthCodeGrant;
    }
    
    @Bean
    public AuthorizationData authorizationData(OAuthDesktopMobileAuthCodeGrant authentication){
        AuthorizationData authorizationData = new AuthorizationData();
        authorizationData.setAuthentication(authentication);
        authorizationData.setCustomerId(new Long(bingAdsCustomerId));
        authorizationData.setAccountId(new Long(bingAdsAccountId));
        authorizationData.setDeveloperToken(bingAdsDeveloperToken);
        return authorizationData;
    }

    @Bean
    public BulkServiceManager bulkServiceManager(AuthorizationData authorizationData){
        BulkServiceManager bulkServiceManager = new BulkServiceManager(authorizationData);
        bulkServiceManager.setStatusPollIntervalInMilliseconds(5000);
        return bulkServiceManager;
    }
}

Step 2: Setup Bing Adwords Service

package com.integration.adwords.service;

import com.microsoft.bingads.AuthorizationData;
import com.microsoft.bingads.bulk.*;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;

/**
 * Created by tmichels on 7/31/15.
 */

@Service
public class BingAdwordService {

    private static final Logger logger = org.slf4j.LoggerFactory.getLogger(BingAdwordService.class);

    private static final String OUTPUTFILENAME="bingAdwords.csv";
    private static final String FILEDIRECTORY="adwords/bing/";

    @Autowired
    BulkServiceManager bulkService;

    public void submitAndDownloadWithBulkServiceManager() throws IOException, URISyntaxException {
        try {
            logger.info("Start BingAdwordService submitAndDownloadWithBulkServiceManager");
            SubmitDownloadParameters submitDownloadParameters = new SubmitDownloadParameters();
            submitDownloadParameters.setCampaignIds(null);
            ArrayList<DataScope> dataScope = new ArrayList<DataScope>();
            dataScope.add(DataScope.ENTITY_DATA);
            submitDownloadParameters.setDataScope(dataScope);
            submitDownloadParameters.setFileType(DownloadFileType.CSV);
            submitDownloadParameters.setLastSyncTimeInUTC(null);
            ArrayList<BulkDownloadEntity> bulkDownloadEntities = new ArrayList<BulkDownloadEntity>();
            bulkDownloadEntities.add(BulkDownloadEntity.CAMPAIGNS);
            bulkDownloadEntities.add(BulkDownloadEntity.AD_GROUPS);
            bulkDownloadEntities.add(BulkDownloadEntity.KEYWORDS);
            bulkDownloadEntities.add(BulkDownloadEntity.ADS);
            submitDownloadParameters.setEntities(bulkDownloadEntities);

            BulkDownloadOperation bulkDownloadOperation = bulkService.submitDownloadAsync(submitDownloadParameters, null).get();

            BulkOperationStatus<DownloadStatus> downloadStatus;
            int waitTime = 5000;

            for (int i = 0; i < 24; i++) {
                Thread.sleep(waitTime);

                downloadStatus = bulkDownloadOperation.getStatusAsync(null).get();

                if (downloadStatus.getStatus() == DownloadStatus.COMPLETED) {
                    break;
                }
            }

            File resultFile = bulkDownloadOperation.downloadResultFileAsync(
                    new File(FILEDIRECTORY),
                    OUTPUTFILENAME,
                    true,
                    true,  // Set this value true if you want to overwrite the same file.
                    null).get();

            logger.info("Download BingAdwords file success %s\n", resultFile.getName());

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

Apache Camel Kafka Spring Integration

1. Configure Camel Kafka Component

package com.integration.camel.component;

import org.apache.camel.component.kafka.KafkaComponent;
import org.apache.camel.component.kafka.KafkaConfiguration;
import org.apache.camel.component.kafka.KafkaEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Created by tmichels on 9/3/15.
 */

@Configuration
public class KafkaCamelComponent {
    
    @Bean
    public KafkaEndpoint kafkaEndpoint(){
        KafkaEndpoint kafkaEndpoint = new KafkaEndpoint();
        kafkaEndpoint.setZookeeperHost("localhost");
        kafkaEndpoint.setZookeeperPort(2181);
        kafkaEndpoint.setTopic("test");
        return kafkaEndpoint;
    }

    @Bean
    public KafkaComponent kafkaComponent(KafkaEndpoint kafkaEndpoint){
        KafkaComponent kafkaComponent = new KafkaComponent();
        kafkaComponent.setEndpointClass(kafkaEndpoint.getClass());
        return kafkaComponent;
    }
}

2. Configure Kafka Consume and Producer Route

package com.integration.camel.route;

import org.apache.camel.builder.RouteBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Created by tmichels on 9/3/15.
 */

@Configuration
public class KafkaCamelRoute {

    @Bean(name = "KafkaRouteProducer")
    public RouteBuilder kafkaRouteProducer() {
        return new RouteBuilder() {
            public void configure() {
                from("direct:kafkaRoute").to("kafka:localhost:9092?topic=test&zookeeperHost=localhost&zookeeperPort=2181&groupId=group1&serializerClass=kafka.serializer.StringEncoder").bean(kafkaOutputBean.class);
            }
        };
    }

    @Bean(name="KafkaRouteConsumer")
    public RouteBuilder kafkaRouteConsumer() {
        return new RouteBuilder() {
            public void configure() {
                from("kafka:localhost:9092?topic=test&zookeeperHost=localhost&zookeeperPort=2181&groupId=group1&serializerClass=kafka.serializer.StringEncoder").bean(kafkaOutputBean.class);
            }
        };
    }

    public static class kafkaOutputBean {
        public void printKafkaBody(String body) {
            System.out.println("KafkaBody result >>>>> " + body);
        }
    }
}

3. Start Kafka test by adding routes to CamelContext and starting

package com.integration.camel.route;

import com.integration.camel.component.KafkaCamelComponent;
import com.integration.camel.context.CamelContextConfig;
import org.apache.camel.CamelContext;
import org.apache.camel.EndpointInject;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.kafka.KafkaComponent;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * Created by tmichels on 9/3/15.
 */

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {KafkaCamelComponent.class,
                                KafkaCamelRoute.class,
                                CamelContextConfig.class})
public class KafkaCamelIntegrationTest {

    @Autowired
    CamelContext camelContext;

    @Autowired
    KafkaComponent kafkaComponent;

    @Autowired
    @Qualifier("KafkaRouteProducer")
    RouteBuilder kafkaRouteProducer;

    @Autowired
    @Qualifier("KafkaRouteConsumer")
    RouteBuilder kafkaRouteConsumer;

    @EndpointInject(uri = "direct:kafkaRoute")
    ProducerTemplate kafkaProducer;
    
    @Before
    public void before() throws Exception {
        camelContext.addRoutes(kafkaRouteProducer);
        camelContext.addRoutes(kafkaRouteConsumer);
    }

    @Test
    public void testKafkaRoute(){
        kafkaProducer.sendBody("direct:KafkaRouteProducer", "testKafkaMessage");
        try {
            camelContext.start();
            camelContext.stop();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
%d bloggers like this: