Salesforce Platform Events Streaming using Spring Boot

Create EmpConnector connection
The first thing is to create a configuration that on startup will connect to the EmpConnector and start listing for incoming events on a specific topic.

1. Set all your Bayeux Parameters (bayeuxParameters)
2. Create EmpConnector connection (empConnector)
3. Start listening to topic (startAndPublishAsyncEventToExchange)
4. Adding listeners to the topic (startAndPublishAsyncEventToExchange)

package com.app.core.events;

import com.app.core.service.IncomingRequestHandler;
import com.app.utils.AppPlanUtils;
import com.salesforce.emp.connector.BayeuxParameters;
import com.salesforce.emp.connector.EmpConnector;
import com.salesforce.emp.connector.TopicSubscription;
import com.salesforce.emp.connector.example.LoggingListener;
import org.cometd.bayeux.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;

import java.net.URL;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;

import static com.salesforce.emp.connector.LoginHelper.login;

@Configuration
@ConfigurationProperties
@Lazy
public class SalesforceEventConfig {

    private static Logger itsLogger = LoggerFactory.getLogger(SalesforceEventConfig.class);

    @Value("${salesforce.username}")
    private String username;

    @Value("${salesforce.password}")
    private String password;

    @Value("${salesforce.token}")
    private String token;

    @Value("${salesforce.baseurl}")
    private String url;

    @Value("${salesforce.version:39.0}")
    private String version;

    @Autowired
    protected IncomingRequestHandler itsReqHandler;

    private static final Integer WAIT_TIME = 2;
    private static final Integer START_UP_WAIT_TIME = 5;
    private static final String EVENT_NAME = "App_Events__e";

    @Bean
    public BayeuxParameters bayeuxParameters() throws Exception {
        String theUrlEnv = AppUtils.getConfigVar("SALESFORCE_URL");
        String passwordAndToken = password + token;
        if (theUrlEnv != null) {
            url = getNextToken(0, theUrlEnv);
            username = getNextToken(url.length() + version.length() + 2, theUrlEnv);
            passwordAndToken = theUrlEnv.substring(url.length() + version.length() + username.length() + 3);
            itsLogger.info("Found SALESFORCE_URL to parse. url={}, version={}, username={}", url, version, username);
            url = url.split("/services/Soap/u/")[0];
        }

        BayeuxParameters bayeuxParams = getBayeuxParamWithSpecifiedAPIVersion(version);
        BayeuxParameters bayeuxParameters = login(new URL(url),username, passwordAndToken, bayeuxParams);
        return bayeuxParameters;
    }

    @Bean
    public EmpConnector empConnector(BayeuxParameters bayeuxParameters){
        itsLogger.debug("BayeuxParameters url {} version {}", bayeuxParameters.host(), bayeuxParameters.version());
        EmpConnector empConnector = new EmpConnector(bayeuxParameters);
        itsLogger.debug("EmpConnector isConnected {} isHandshook {}", empConnector.isConnected(), empConnector.isHandshook());
        return empConnector;
    }

    private static BayeuxParameters getBayeuxParamWithSpecifiedAPIVersion(String apiVersion) {
        BayeuxParameters params = new BayeuxParameters() {

            @Override
            public String version() {
                return apiVersion;
            }

            @Override
            public String bearerToken() {
                return null;
            }

        };
        return  params;
    }

    private String getNextToken(int startIdx, String origStr) {
        int theIdx = origStr.indexOf("|", startIdx);
        if (theIdx > 0) {
            return origStr.substring(startIdx, theIdx);
        } else {
            return origStr.substring(startIdx);
        }
    }

    @Bean
    public TopicSubscription startAndPublishAsyncEventToExchange(EmpConnector empConnector)  {
        TopicSubscription subscription = null;
        try {
            long replayFrom = EmpConnector.REPLAY_FROM_TIP;
            itsLogger.debug("Setup event consumer with replyFrom {}", replayFrom);

            SalesforceEventPayload eventPayload = new SalesforceEventPayload();
            Consumer<Map<String, Object>> consumer = event -> {
                eventPayload.setPayload(event);
                itsReqHandler.handleRequest(eventPayload.getPayload());
            };

            LoggingListener loggingListener = new LoggingListener(true, true);
            itsLogger.debug("Adding event listeners");
            empConnector.addListener(Channel.META_CONNECT, loggingListener)
                    .addListener(Channel.META_HANDSHAKE, loggingListener)
                    .addListener(Channel.META_DISCONNECT, loggingListener)
                    .addListener(Channel.META_SUBSCRIBE, loggingListener)
                    .addListener(Channel.META_UNSUBSCRIBE, loggingListener)
                    .addListener(Channel.META_DISCONNECT, loggingListener)
                    .addListener(Channel.SERVICE, loggingListener);

            itsLogger.debug("Starting Event Bus");
            empConnector.start().get(START_UP_WAIT_TIME, TimeUnit.SECONDS);

            itsLogger.debug("Subscribing to event {}", EVENT_NAME);
            subscription = empConnector.subscribe("/event/" + EVENT_NAME, replayFrom, consumer).get(WAIT_TIME, TimeUnit.SECONDS);

            itsLogger.debug(String.format("Subscribed: %s", subscription));

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

        return subscription;
    }
}

Basic POJO to serialize events received for a specific topic

Note: as the ‘payload’ tag has some extra tags I remove them before serializing the payload to POJO.

package com.app.core.events;

import com.app.db.model.UserServiceRequestEvent;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.Serializable;
import java.util.Map;

public class SalesforceEventPayload implements Serializable{

    private static final String SALESFORCE_EVENT_KEY = "Event_Data__c=";

    private static Logger itsLogger = LoggerFactory.getLogger(SalesforceEventPayload.class);

    public SalesforceEventPayload() {}

    private UserServiceRequestEvent payload;

    public UserServiceRequestEvent getPayload() {
        return payload;
    }

    public void setPayload(Map<String, Object> fieldMappings) {
        ObjectMapper theMapper = new ObjectMapper();
        theMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        theMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
        theMapper.configure(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY, true);
        theMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
        theMapper.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true);
        if (fieldMappings.containsKey("payload")){
            String eventPayload = fieldMappings.get("payload").toString();
            String splitEventString = eventPayload.toString().split(SALESFORCE_EVENT_KEY)[1];
            String removeLastTwo = splitEventString.substring(1, splitEventString.length() - 2);
            String unescapeJson = StringEscapeUtils.unescapeJson(removeLastTwo);
            try{
                payload = theMapper.readValue(unescapeJson, UserServiceRequestEvent.class);
            } catch (JsonParseException e) {
                itsLogger.error("Event json parse exception {}", e.getMessage());
            } catch (JsonMappingException e) {
                itsLogger.error("Event json mapping exception {}", e.getMessage());
            } catch (IOException e) {
                itsLogger.error("Event io exception {}", e.getMessage());
            }
        }
    }
}

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

Spring MVC AngularJS Address Book

Try it: http://cloudsole-angular.herokuapp.com
Clone it: https://github.com/thysmichels/cloudsole-angular

1. Address Book Model
2. Address Book Service
3. Address Book Service Implementation
4. Address Book Controller
5. Address Book Angular Controller
6. Address Book HTML

1. Address Book Model

package com.cloudsole.angular.model;

/**
 * Created by tmichels on 8/3/14.
 */
public class AddressBook {

    private long id;
    private String firstName;
    private String lastName;
    private String phone;
    private String email;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

2. Address Book Service

package com.cloudsole.angular.service;

import com.cloudsole.angular.model.AddressBook;

import java.util.List;

/**
 * Created by tmichels on 8/3/14.
 */
public interface AddressBookService {
     List<AddressBook> viewAllAddressBook();
     void createAddressBook(AddressBook addressBook);
     void updateAddressBook(int pos, AddressBook updateAddressBook);
     void deleteAddressBook(int id);
     void deleteAllAddressBook();
     AddressBook findAddressBook(int id);
}

3. Address Book Service Implementation

package com.cloudsole.angular.service;

import com.cloudsole.angular.model.AddressBook;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by tmichels on 8/3/14.
 */
@Service
public class AddressBookServiceImpl implements AddressBookService {

    List<AddressBook> addressBooks = new ArrayList<AddressBook>();
    private static Long id = 0L;

    @Override
    public List<AddressBook> viewAllAddressBook() {
        return addressBooks;
    }

    @Override
    public void createAddressBook(AddressBook addressBook) {
        addressBook.setId(id);
        addressBooks.add(addressBook);
        ++id;
    }

    @Override
    public void updateAddressBook(int pos, AddressBook updateAddressBook) {
        addressBooks.set(pos, updateAddressBook);
    }

    @Override
    public void deleteAddressBook(int id) {
        addressBooks.remove(id);
    }

    @Override
    public void deleteAllAddressBook() {
        addressBooks.clear();
        id = 0L;
    }

    @Override
    public AddressBook findAddressBook(int id) {
        return addressBooks.get(id);
    }
}

4. Address Book Controller

package com.cloudsole.angular.controller;

import com.cloudsole.angular.model.AddressBook;
import com.cloudsole.angular.service.AddressBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * Created by tmichels on 8/3/14.
 */

@Controller
@RequestMapping("/address")
public class AddressBookController {

    @Autowired
    AddressBookService addressBookService;

    @RequestMapping(value = "/all.json", method = RequestMethod.GET)
    public @ResponseBody List<AddressBook> viewAllAddressBook(){
        return addressBookService.viewAllAddressBook();
    }

    @RequestMapping(value = "/add", method = RequestMethod.POST)
    public @ResponseBody void addAddressBookEntry(@RequestBody AddressBook addressBook){
        addressBookService.createAddressBook(addressBook);
    }

    @RequestMapping(value = "/delete/{id}", method = RequestMethod.DELETE)
    public @ResponseBody void deleteAddressBookEntry(@PathVariable("id") String id){
        addressBookService.deleteAddressBook(Integer.valueOf(id));
    }

    @RequestMapping(value = "/update/{pos}", method = RequestMethod.PUT)
    public @ResponseBody void updateAddressBook(@RequestBody AddressBook addressBook, @PathVariable("pos") String pos){
        addressBookService.updateAddressBook(Integer.valueOf(pos), addressBook);
    }

    @RequestMapping(value="/delete/all", method = RequestMethod.DELETE)
    public @ResponseBody void deleteAllAddressBook(){
        addressBookService.deleteAllAddressBook();
    }

    @RequestMapping("/layout")
    public String getTodoPartialPage() {
        return "addressbook/layout";
    }
}

5. Address Book Angular Controller

/**
 * Created by tmichels on 8/3/14.
 */


var AddressBookController = function($scope, $http){

    $scope.editMode = false;
    $scope.position = '';

    $scope.viewAllAddressBook = function(){
        $http.get('address/all.json').success(function(response){
            $scope.addressBooks = response;
        })
    }

    $scope.resetAddressBookField = function(){
        $scope.ab.firstName='';
        $scope.ab.lastName='';
        $scope.ab.phone = '';
        $scope.ab.email = '';
        $scope.editMode = false;
    }

    $scope.addAddressBook = function(ab) {
        $http.post('address/add', ab).success(function(response){
            $scope.viewAllAddressBook();
            $scope.ab.firstName='';
            $scope.ab.lastName='';
            $scope.ab.phone = '';
            $scope.ab.email = '';
        }).error(function(response){
            console.log(response);
        })
    }

    $scope.updateAddressBook = function(ab) {
        $http.put('address/update/'+$scope.position, ab).success(function(response){
            $scope.ab.firstName='';
            $scope.ab.lastName='';
            $scope.ab.phone = '';
            $scope.ab.email = '';
            $scope.viewAllAddressBook();
            $scope.editMode = false;
        }).error(function(response){
            console.log(response);
        })
    }

    $scope.deleteAddressBook = function(id) {
        $http.delete('address/delete/' + id).success(function(response){
            $scope.viewAllAddressBook();
        }).error(function(response){
            console.log(response);
        })
    }

    $scope.deleteAllAddressBook = function(){
        $http.delete('address/delete/all').success(function(response){
            $scope.viewAllAddressBook();
        })
    }

    $scope.editAddressBook = function(pos, addressBook){
        $scope.position = pos;
        $scope.ab = addressBook;
        $scope.editMode = true;
    }

    $scope.viewAllAddressBook();
}

6. Address Book HTML

<div class="alert alert-error" ng-show="error">{{errorMessage}}</div>
<div class="row">
    <form ng-submit="addAddressBook(ab)">
        <div class="col-lg-8">
            <input class="form-control" placeholder="Enter First Name" type="text" ng-model="ab.firstName" required min="1" />
            <input class="form-control" placeholder="Enter Last Name" type="text" ng-model="ab.lastName" required min="1" />
            <input class="form-control" placeholder="Enter Phone" type="text" ng-model="ab.phone" required min="1" />
            <input class="form-control" placeholder="Enter Email" type="text" ng-model="ab.email" required min="1" />
        </div>
    </form>

    <button class="btn btn-primary" ng-disabled="!ab" ng-hide="editMode" ng-click="addAddressBook(ab)">Add Entry</button>
    <button type="btn btn-primary" class="btn btn-primary"
            ng-disabled="!ab" ng-show="editMode"
            ng-click="updateAddressBook(ab)">Save</button>
    <button type="btn btn-primary" class="btn btn-primary" ng-click="resetAddressBookField()">Reset</button>
</div>
<hr />

<div class="row">
    <div class="col-lg-8">
        <div class="form-group">
            <div class="input-group">
                <input type="text" class="form-control" placeholder="Search" id="search-query-3" ng-model="searchAddressBook">
                  <span class="input-group-btn">
                    <button type="submit" class="btn"><span class="fui-search"></span></button>
                  </span>
            </div>
        </div>
    </div>
</div>
<hr />

<div class="alert alert-info" style="width:400px;margin-left:100px;" ng-show="addressBooks.length == 0">
    No address book entry found
</div>
<table class="table table-bordered table-striped" ng-show="addressBooks.length > 0">
    <thead>
    <tr>
        <th style="text-align: center; width: 25px;">Delete</th>
        <th style="text-align: center; width: 25px;">Update</th>
        <th style="text-align: center;">First Name</th>
        <th style="text-align: center;">Last Name</th>
        <th style="text-align: center;">Phone Number</th>
        <th style="text-align: center;">Email</th>
    </tr>
    </thead>
    <tbody>
    <tr ng-repeat="addressBook in addressBooks">
        <td  style="width:70px;text-align:center;"><button class="btn btn-mini btn-danger" ng-click="deleteAddressBook(addressBooks.indexOf(addressBook))">Delete</button></td>
        <td  style="width:70px;text-align:center;"><button class="btn btn-mini btn-danger" ng-click="editAddressBook(addressBooks.indexOf(addressBook), addressBook)">Update</button></td>
        <td>{{addressBook.firstName}}</td><td>{{addressBook.lastName}}</td><td>{{addressBook.phone}}</td><td>{{addressBook.email}}</td>
    </tr>
    </tbody>
</table>
<button class="btn btn-danger"  ng-show="addressBooks.length >= 1" ng-click="deleteAllAddressBook()">Delete All Address Book</button>

Spring MVC AngularJs Todo List

Try it: http://cloudsole-angular.herokuapp.com
Clone it: https://github.com/thysmichels/cloudsole-angular

1. Todo Service
2. Todo Service Implementation
3. Todo Spring MVC Controller
4. Angular App.js
5. Angular TodoController
6. Todo html page

1. Todo Service

package com.cloudsole.angular.service;

import java.util.List;

/**
 * Created by tmichels on 8/1/14.
 */
public interface TodoService {
    public List<String> allTodos();
    public void addTodo(String todo);
    public void deleteTodo(String todo);
    public void deleteAll();
    public void updateTodo(int position, String todo);
}

2. Todo Service Implementation

package com.cloudsole.angular.service;

import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by tmichels on 8/1/14.
 */

@Service
public class TodoServiceImpl implements TodoService {

    List<String> todos = new ArrayList<String>();

    @Override
    public List<String> allTodos() {
        return todos;
    }

    @Override
    public void addTodo(String todo) {
        todos.add(todo);
    }

    @Override
    public void deleteTodo(String todo) {
        if (todos.contains(todo)){
            todos.remove(todo);
        }
    }

    @Override
    public void deleteAll() {
        todos.clear();
    }


    @Override
    public void updateTodo(int position, String todo) {
        todos.set(position, todo);
    }
}

3. Todo Spring MVC Controller

package com.cloudsole.angular.controller;

import com.cloudsole.angular.service.TodoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;

/**
 * Created by tmichels on 8/1/14.
 */

@Controller
@RequestMapping("/todo")
public class TodoController {

    @Autowired
    private TodoService todoService;

    @RequestMapping(value = "/all.json")
    public @ResponseBody List<String> viewAllTodos(){
        return todoService.allTodos();
    }

    @RequestMapping(value = "/add/{todo}", method = RequestMethod.POST)
    public @ResponseBody void addTodo(@PathVariable("todo") String todo){
        todoService.addTodo(todo);
    }

    @RequestMapping(value = "/delete/{todo}", method = RequestMethod.DELETE)
    public @ResponseBody void deleteTodo(@PathVariable("todo") String todo){
        todoService.deleteTodo(todo);
    }

    @RequestMapping(value = "/deleteAll", method = RequestMethod.DELETE)
    public @ResponseBody void deleteAllTodo(){
        todoService.deleteAll();
    }

    @RequestMapping(value="/update/{position}/{todo}", method = RequestMethod.PUT)
    public @ResponseBody void updateTodo(@PathVariable("position") String position, @PathVariable("todo") String todo){
        todoService.updateTodo(Integer.valueOf(position), todo);
    }

    @RequestMapping("/layout")
    public String getTodoPartialPage() {
        return "todo/layout";
    }
}

4. Angular App.js

'use strict';

var AngularSpringApp = {};

var App = angular.module('AngularSpringApp', ['AngularSpringApp.filters', 'AngularSpringApp.services', 'AngularSpringApp.directives', 'ngRoute', 'ui.bootstrap', 'ngTable', 'ui.ace', 'angularFileUpload']);

// Declare app level module which depends on filters, and services
App.config(['$routeProvider', function ($routeProvider) {

    $routeProvider.when('/todo', {
        templateUrl: 'todo/layout',
        controller: TodoController
    });
    $routeProvider.when('/address', {
        templateUrl: 'address/layout',
        controller: AddressBookController
    });
    $routeProvider.when('/table', {
        templateUrl: 'table/layout',
        controller: TableController
    });
    $routeProvider.when('/file', {
        templateUrl: 'file/layout',
        controller: FileController
    });
    $routeProvider.when('/editor', {
        templateUrl: 'editor/layout',
        controller: EditorController
    });
    $routeProvider.when('/rest', {
        templateUrl: 'restangular/layout',
        controller: RestController
    });
    $routeProvider.when('/force', {
        templateUrl: 'force/layout',
        controller: ForceController
    });

    $routeProvider.otherwise({redirectTo: '/todo'});
}]);

5. Angular TodoController


var TodoController = function($scope, $http){

    $scope.editMode = false;
    $scope.position = '';

    $scope.getAllTodos = function(){
        $scope.resetError();
        $http.get('todo/all.json').success(function(response){
            $scope.todos = response;
        }).error(function() {
            $scope.setError('Could not display all todos');
        });
    }

    $scope.addTodo = function(newTodo){
        $scope.resetError();
        $http.post('todo/add/' + newTodo).success(function(response){
            $scope.getAllTodos();
        }).error(function() {
            $scope.setError('Could add todo');
        });
        $scope.todoName = '';
    }

    $scope.deleteTodo = function(deleteTodo){
        $scope.resetError();
        $http.delete('todo/delete/'+deleteTodo).success(function(response){
            $scope.getAllTodos();
        }).error(function() {
            $scope.setError('Could not delete todo');
        });
    }

    $scope.deleteAllTodo = function(){
        $scope.resetError();
        $http.delete('todo/deleteAll').success(function(response){
            $scope.getAllTodos();
        }).error(function() {
            $scope.setError('Could not delete all todos');
        })
    }

    $scope.editTodo = function(position, todo){
        $scope.resetError();
        $scope.todoName = todo;
        $scope.position = position;
        $scope.editMode = true;
    }

    $scope.updateTodo = function(updateTodo){
        $scope.resetError();
        $http.put('todo/update/'+ $scope.position +'/'+updateTodo).success(function(response){
            $scope.getAllTodos();
            $scope.position = '';
            $scope.todoName = '';
            $scope.editMode = false;
        }).error(function(){
            $scope.setError('Could not update todo');
         })
    }

    $scope.resetTodoField = function() {
        $scope.resetError();
        $scope.todoName = '';
        $scope.editMode = false;
    };

    $scope.resetError = function() {
        $scope.error = false;
        $scope.errorMessage = '';
    };

    $scope.setError = function(message) {
        $scope.error = true;
        $scope.errorMessage = message;
    };

    $scope.getAllTodos();
}

6. Todo html page

<div class="alert alert-error" ng-show="error">{{errorMessage}}</div>
<div class="row">
    <form ng-submit="addTodo(todoName)">
    <div class="col-lg-8">
     <input class="form-control" placeholder="Enter Todo" type="text" ng-model="todoName" required min="1" />
    </div>
   </form>

    <button class="btn btn-primary" ng-disabled="!todoName" ng-hide="editMode" ng-click="addTodo(todoName)">Add Todo</button>
    <button type="btn btn-primary" class="btn btn-primary"
            ng-disabled="!todoName" ng-show="editMode"
            ng-click="updateTodo(todoName)">Save</button>
    <button type="btn btn-primary" class="btn btn-primary" ng-click="resetTodoField()">Reset</button>
</div>
 <hr />

<div class="row">
    <div class="col-lg-8">
        <div class="form-group">
            <div class="input-group">
                <input type="text" class="form-control" placeholder="Search" id="search-query-3" ng-model="searchTodo" typeahead="todo for todo in todos | filter:$viewValue | limitTo:8">
                  <span class="input-group-btn">
                    <button type="submit" class="btn"><span class="fui-search"></span></button>
                  </span>
            </div>
        </div>
    </div>
</div>
<hr />

<div class="alert alert-info" style="width:400px;margin-left:100px;" ng-show="todos.length == 0">
    No todos found
</div>
<table class="table table-bordered table-striped" ng-show="todos.length > 0">
    <thead>
    <tr>
        <th style="text-align: center; width: 25px;">Delete</th>
        <th style="text-align: center; width: 25px;">Update</th>
        <th style="text-align: center;">Todo</th>
    </tr>
    </thead>
    <tbody>
    <tr ng-repeat="todo in todos | filter:searchTodo">
        <td  style="width:70px;text-align:center;"><button class="btn btn-mini btn-danger" ng-click="deleteTodo(todo)">Delete</button></td>
        <td  style="width:70px;text-align:center;"><button class="btn btn-mini btn-danger" ng-click="editTodo(todos.indexOf(todo), todo)">Update</button></td>
        <td>{{todo}}</td>
    </tr>
    </tbody>
</table>
<button class="btn btn-danger"  ng-show="todos.length >= 1" ng-click="deleteAllTodo()">Delete All Todos</button>

Heroku OAuth 2.0 Scribe Spring MVC Example

ApplicationContext.xml defines herokuServiceConfig and herokuServiceProvider Bean

  <bean id="herokuServiceConfig" class="com.example.oauth.OAuthServiceConfig">
		<constructor-arg value="xxx" />
		<constructor-arg value="xxx"/>
		<constructor-arg value="https://www.example.com/oauth/heroku"/>
		<constructor-arg value="com.example.oauth.HerokuOauthApi"/>
	</bean>
	
	<bean id="herokuServiceProvider" class="com.example.oauth.OAuthServiceProvider">
		<constructor-arg name="config" ref="herokuServiceConfig" />
	</bean>

Spring MVC Heroku OAuth Controller

package com.example.oauth.controller;

import static org.springframework.web.context.request.RequestAttributes.SCOPE_SESSION;

import java.util.Map;

import javax.servlet.http.HttpSession;

import org.scribe.model.Token;
import org.scribe.model.Verifier;
import org.scribe.oauth.OAuthService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.WebRequest;

import com.example.oauth.OAuthServiceProvider;

/**
 * @author tmichels
 */

@Controller
@RequestMapping("/oauth/heroku")
public class HerokuController {
	
	@Autowired
	@Qualifier("herokuServiceProvider")
	private OAuthServiceProvider herokuServiceProvider;
	
	private static final Token EMPTY_TOKEN = null;
	
	@RequestMapping(value="/login-heroku", method=RequestMethod.GET)
	 public String loginToHeroku(Map<String, Object> map, WebRequest request) {
			OAuthService service = herokuServiceProvider.getService();
			String authUrl = service.getAuthorizationUrl(EMPTY_TOKEN);
			System.out.println("RequestToken: " + authUrl );
			return "redirect:" + authUrl;
    }
	
	@RequestMapping(value={""}, method = RequestMethod.GET)
	public String callback(@RequestParam(value="oauth_token", required=false) String oauthToken,
			@RequestParam(value="code", required=false) String oauthVerifier, WebRequest request, Map<String, Object> map) {

		OAuthService service = herokuServiceProvider.getService();

		// getting access token
		Verifier verifier = new Verifier(oauthVerifier);
		Token accessToken = service.getAccessToken(EMPTY_TOKEN, verifier);

		// store access token as a session attribute
		request.setAttribute("oauthAccessToken", accessToken, SCOPE_SESSION);

		ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
		HttpSession session = attr.getRequest().getSession(false); //create a new session
		session.setAttribute("accessToken",accessToken);
		
		return "settings";
	}
		
}

HerokuOauthApi extends DefaultApi20

package com.example.oauth;

import org.scribe.builder.api.DefaultApi20;
import org.scribe.extractors.AccessTokenExtractor;
import org.scribe.extractors.JsonTokenExtractor;
import org.scribe.model.OAuthConfig;
import org.scribe.model.Verb;

public class HerokuOauthApi extends DefaultApi20{

	private static final String ACCESSTOKEN = "https://id.heroku.com/oauth/token";

	@Override
	public String getAccessTokenEndpoint() {
		return ACCESSTOKEN;
	}

	@Override
	public String getAuthorizationUrl(OAuthConfig config) {
		return String.format("https://id.heroku.com/oauth/authorize?client_id=%s&response_type=code&scope=global", config.getApiKey());
	}
	
	 @Override
	 public Verb getAccessTokenVerb(){
	       return Verb.POST;
	 }
	 
	 @Override
	 public AccessTokenExtractor getAccessTokenExtractor() {
	    return new JsonTokenExtractor();
	 }
}

Activiti BPM Tutorial – Spring Configuration

Activiti Spring Configuration
1. Activiti Configuration using XML
1.1 Activiti Configuration XML Test
2. Activiti Configuration using Annotations
2.1 Activiti Configuration Annotation Test

1. Activiti Configuration using XML

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans    
                           http://www.springframework.org/schema/beans/spring-beans.xsd 
                           http://www.springframework.org/schema/context  
                           http://www.springframework.org/schema/context/spring-context-2.5.xsd 
                           http://www.springframework.org/schema/tx       
                           http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

	<bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
			<property name="driverClass" value="org.h2.Driver" />
			<property name="url" value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000" />
			<property name="username" value="sa" />
			<property name="password" value="" />
	</bean>

	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
		<property name="databaseType" value="h2" />
		<property name="dataSource" ref="dataSource" />
		<property name="transactionManager" ref="transactionManager" />
		<property name="databaseSchemaUpdate" value="true" />
		<property name="deploymentResources"
			value="classpath*:diagrams/underwriting_queue.bpmn" />
		<property name="history" value="audit" />
		<property name="jobExecutorActivate" value="false" />
	</bean>

	<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
		<property name="processEngineConfiguration" ref="processEngineConfiguration" />
	</bean>

	<bean id="repositoryService" factory-bean="processEngine"
		factory-method="getRepositoryService" />
	<bean id="runtimeService" factory-bean="processEngine"
		factory-method="getRuntimeService" />
	<bean id="taskService" factory-bean="processEngine"
		factory-method="getTaskService" />
	<bean id="historyService" factory-bean="processEngine"
		factory-method="getHistoryService" />
	<bean id="managementService" factory-bean="processEngine"
		factory-method="getManagementService" />
</beans>

1.1 Activiti Configuration XML Test

package com.nuke.activiti.spring;

import static org.junit.Assert.assertEquals;

import java.util.HashMap;
import java.util.Map;

import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.nuke.activiti.common.AbstractTest;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-test-application-context.xml")
public class SpringTest extends AbstractTest {

	@Autowired
	private RuntimeService runtimeService;

	@Autowired
	private TaskService taskService;

	@Test
	public void simpleProcessTest() {
		Map<String, Object> variableMap = new HashMap<String, Object>();
		variableMap.put("LoanId", 123456789);
		runtimeService.startProcessInstanceByKey("underwritingQueue", variableMap);
		Task task = taskService.createTaskQuery().singleResult();
		assertEquals("Unassigned Loan", task.getName());
		taskService.complete(task.getId());
		assertEquals(1, runtimeService.createProcessInstanceQuery().count());
	}
}

2. Activiti Configuration using Annotations

package com.nuke.config;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.impl.HistoryServiceImpl;
import org.activiti.engine.impl.ManagementServiceImpl;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.RuntimeServiceImpl;
import org.activiti.engine.impl.TaskServiceImpl;
import org.activiti.engine.impl.history.HistoryLevel;
import org.activiti.engine.impl.interceptor.SessionFactory;
import org.activiti.spring.SpringProcessEngineConfiguration;
import org.activiti.spring.annotations.AbstractActivitiConfigurer;
import org.activiti.spring.annotations.EnableActiviti;
import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import com.nuke.ldap.LDAPConnectionParams;
import com.nuke.ldap.LDAPGroupManagerFactory;
import com.nuke.ldap.LDAPUserManagerFactory;

@Configuration
@EnableActiviti
@EnableTransactionManagement(proxyTargetClass = true)
//@PropertySource("file:conf/lc-merged.properties")
public class ActivitConfiguration {
 
    @Bean
    public DataSource inMemoryDataSource() {
        BasicDataSource basicDataSource = new BasicDataSource();
        basicDataSource.setUsername("sa");
        basicDataSource.setUrl("jdbc:h2:mem:activiti");
        basicDataSource.setDefaultAutoCommit(false);
        basicDataSource.setDriverClassName(org.h2.Driver.class.getName());
        basicDataSource.setPassword("");
        return basicDataSource;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(DataSource inMemoryDataSource) {
	  LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
      localContainerEntityManagerFactoryBean.setDataSource(inMemoryDataSource);
      localContainerEntityManagerFactoryBean.setPackagesToScan(new String[]{"com.nuke.model"});
      localContainerEntityManagerFactoryBean.setPersistenceUnitName("nuke");
      HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
      
      jpaVendorAdapter.setGenerateDdl(false);
      jpaVendorAdapter.setShowSql(false);
      jpaVendorAdapter.setDatabasePlatform("org.hibernate.dialect.H2Dialect");
      localContainerEntityManagerFactoryBean.setJpaVendorAdapter(jpaVendorAdapter);
      return localContainerEntityManagerFactoryBean;
    }

    @Bean
    public PlatformTransactionManager jpaTransactionManager(EntityManagerFactory entityManagerFactoryBean) {
        return new JpaTransactionManager(entityManagerFactoryBean);
    } 
    
    @Value("${LC_NUKE_LDAP_URL:localhost}")
	private String ldapServer;
	@Value("${LC_NUKE_LDAP_PORT}")
	private String ldapPort;
	@Value("${LC_NUKE_LDAP_USERNAME:uid=admin,ou=system}")
	private String ldapUser;
	@Value("${LC_NUKE_LDAP_PASSWORD:secret}")
	private String ldapPassword;
	
	@Bean
	public LDAPConnectionParams ldapConnection(){
		LDAPConnectionParams ldapConnection = new LDAPConnectionParams();
		ldapConnection.setLdapServer(ldapServer);
		ldapConnection.setLdapPort(10389);
		ldapConnection.setLdapUser(ldapUser);
		ldapConnection.setLdapPassword(ldapPassword);
		return ldapConnection;
	}
    
    
	@Value("${LC_NUKE_MAIL_HOST}")
	private String mailHost;
	@Value("${LC_NUKE_MAIL_USERNAME}")
	private String mailUsername;
	@Value("${LC_NUKE_MAIL_PASSWORD}")
	private String mailServerPassword;
	
    @Bean
    public AbstractActivitiConfigurer abstractActivitiConfigurer(final EntityManagerFactory entityManagerFactoryBean, final PlatformTransactionManager jpaTransactionManager) {
        return new AbstractActivitiConfigurer() {
            @Override
            public void postProcessSpringProcessEngineConfiguration(SpringProcessEngineConfiguration engine) {
            	engine.setDatabaseType("h2");
            	engine.setDataSource(inMemoryDataSource());
                engine.setTransactionManager(jpaTransactionManager);
                engine.setJpaEntityManagerFactory(entityManagerFactoryBean);
                engine.setJpaHandleTransaction(true);
                engine.setJobExecutorActivate(true);
                engine.setJpaCloseEntityManager(false);
                engine.setMailServerDefaultFrom("nucleus@lc.com");
                engine.setMailServerHost(mailHost);
                engine.setMailServerPort(587);
                engine.setMailServerUsername(mailUsername);
                engine.setMailServerPassword(mailServerPassword);
                engine.setMailServerUseSSL(true);
                engine.setMailServerUseTLS(true);
                engine.setRepositoryService(repositoryService());
                engine.setTaskService(taskService());
                engine.setManagementService(managementService());
                engine.setRuntimeService(runtimeService());
                engine.setHistoryService(historyService());
                engine.setHistoryLevel(HistoryLevel.AUDIT);
                Resource[] processResources = new Resource[1] ;
                Resource underwritingQueueProcess = new ClassPathResource("diagrams/underwriting_queue.bpmn");
                processResources[0]=underwritingQueueProcess;
                engine.setDeploymentResources(processResources);
                List<SessionFactory> customSessionFactories = new ArrayList<SessionFactory>();
                customSessionFactories.add(new LDAPGroupManagerFactory(ldapConnection()));
                customSessionFactories.add(new LDAPUserManagerFactory(ldapConnection()));
        		engine.setCustomSessionFactories(customSessionFactories);
                
                engine.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
            }
        };
    }
    
    @Bean
    public RepositoryServiceImpl repositoryService(){
    	RepositoryServiceImpl repositoryService = new RepositoryServiceImpl();
    	return repositoryService;
    }
    
    @Bean
    public TaskServiceImpl taskService(){
    	TaskServiceImpl taskService = new TaskServiceImpl();
    	return taskService;
    }
    
    @Bean
    public ManagementServiceImpl managementService(){
    	ManagementServiceImpl managementService = new ManagementServiceImpl();
    	return managementService;
    }
    
    @Bean
    public RuntimeServiceImpl runtimeService(){
    	RuntimeServiceImpl runtimeService = new RuntimeServiceImpl();
    	return runtimeService;
    }
    
    @Bean
    public HistoryServiceImpl historyService(){
    	HistoryServiceImpl historyService = new HistoryServiceImpl();
    	return historyService;
    }    
}

2.1 Activiti Configuration Annotation Test

package com.nuke.activiti.spring;

import static org.junit.Assert.assertEquals;

import java.util.HashMap;
import java.util.Map;

import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.nuke.config.ActivitConfiguration;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=ActivitConfiguration.class)
public class SpringWithAnnotations {

	@Autowired
	private RuntimeService runtimeService;
	
	@Autowired
	private TaskService taskService;

	@Test
	public void simpleProcessTest() {
		Map<String, Object> variableMap = new HashMap<String, Object>();
		variableMap.put("LoanId", 123456789);
		runtimeService.startProcessInstanceByKey("underwritingQueue", variableMap);
		Task task = taskService.createTaskQuery().singleResult();
		assertEquals("Unassigned Loan", task.getName());
		taskService.complete(task.getId());
		assertEquals(1, runtimeService.createProcessInstanceQuery().count());
	}
}

Spring MVC form checkbox binding

DeployList Model

package com.example.util;

import java.util.List;

public class DeployLists {

	private List<String> deployList;
	
	public List<String> getDeployList() {
		return deployList;
	}

	public void setDeployList(List<String> deployList) {
		this.deployList = deployList;
	}
}

ModelAndView DeployLists

	@RequestMapping("")
	public ModelAndView deployView(){
		ModelAndView mav = new ModelAndView("deploy");
		
		QueryResult<Map> apexClass = loginService.loginToSalesforce().query("Select Id, Name, ApiVersion, Status from ApexClass");
		
		List<String> apexClassNames= new ArrayList<String>();
		for (Map className : apexClass.getRecords()){
			apexClassNames.add(className.get("Name").toString());
		}

		mav.addObject("deployList", apexClassNames);
		mav.addObject("deployLists", new DeployLists());
		return mav;
	}

deploy.jsp

<div class="well clearfix">
	<div class="accordion" id="accordion2">
		<form:form method="POST" commandName="deployLists" action="/login/deploy/result" >
		<div class="accordion-group">
			<div class="accordion-heading">
				<a class="accordion-toggle" data-toggle="collapse" data-parent="#accordion2" href="#collapseOne">
					ApexClass
				</a>
			</div>
			<div id="collapseOne" class="accordion-body collapse in">
				<div class="accordion-inner">
					<form:checkboxes element="li" path="deployList" items="${deployList}"></form:checkboxes></ul>
				</div>
			</div>
		</div>

		</div><br/>
		<button type="submit" class="btn btn-primary">Deploy</button>
		</form:form>
	</div>
</div>

result.jsp

<%@ page language="java" contentType="text/html; charset=US-ASCII"
    pageEncoding="US-ASCII"%>
<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%>

<jsp:include page="header.jsp"/>
		<section class="row-fluid" id="why-adminflare">
			<h3 class="box-header"><i class="icon-fire" style="color: #cd522c"></i>Result</h3>
			<div class=" box well ">
				<c:forEach items="${deployLists.deployList}" var="dl" varStatus="index">
						${dl}
				</c:forEach>
			</div>
		</section>
<jsp:include page="footer.jsp"/>

Spring MVC form with multiple submit buttons

<div class="codeContainer">
	<form method="post" action="/login/apex/save/${apexType}/${currentId}">
		<textarea name="code" id="code">${body}</textarea><br/>
		<div class="btn-group">
			<input type="submit" value="Save" name="save" class="btn btn-primary">
			<input type="submit" value="Share" name="share" class="btn btn-primary">
		</div>
	</form>
</div>
	@SuppressWarnings("unchecked")
    @RequestMapping(method = RequestMethod.POST, value = "/save/{type}/{id}")
    public String updateClassDetail(@PathVariable("id") String id, @PathVariable("type") String type,
                    @RequestParam("code") String code, Map<String, Object> map,
                    @RequestParam(required=false, value="save") String save,
                    @RequestParam(required=false, value="share") String share){

  if (save!=null){
    ....
  } else if (share!=null){
    ...
  } 
}

Create Spring Bean On-Demand using Bean Lookup

Bean Configuration – create prototype method on demand using look-up method

@Configuration
@Lazy(value=true)
public class SalesforceAuthenticationConfigImpl implements SalesforceAuthenticationConfig {

    @Bean(name="loginToProductionSalesforce")
    @Scope(value="prototype")
	public PartnerConnection loginToProductionSalesforce() throws ConnectionException, InterruptedException {
		int retryCounter=1;
		productionPartnerConnection();
		Date now = new Date();		
		while (partnerConnection==null && retryCounter <= RETRYAMOUNT || TimeUnit.MILLISECONDS.toMinutes(now.getTime() - partnerConnection.getServerTimestamp().getTimestamp().getTimeInMillis()) > MINUTESTILLTIMEOUT){
			logger.info("loginToProductionSalesforce retry attempt: " + retryCounter);
			productionPartnerConnection();
        	retryCounter++;
        	Thread.sleep(30*1000);
		}
		return partnerConnection;
	}


    @Bean
	public SalesforceAuthenticationFactory salesforceAuthenicationFactory(){
		return new SalesforceAuthenticationFactory() {
			@Override
			public PartnerConnection createPartnerConnection() throws ConnectionException, InterruptedException {
				return loginToProductionSalesforce();
			}
		};
	}

}

Configuration factory

@Bean
	public SalesforceAuthenticationFactory salesforceAuthenicationFactory(){
		return new SalesforceAuthenticationFactory() {
			@Override
			public PartnerConnection createPartnerConnection() throws ConnectionException, InterruptedException {
				return loginToProductionSalesforce();
			}
		};
	}

Test Class

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={SalesforceAuthenticationConfigImpl.class})
public class SalesforceAuthenticationServiceTest {

	@Autowired SalesforceAuthenticationFactory salesforceAuthenicationFactory;

     @Test
	public void testSalesforceAuthenitcationFactory(){
		try {
			PartnerConnection pConnection = salesforceAuthenicationFactory.createPartnerConnection();
			assertNotNull(pConnection.getConfig().getSessionId());
		} catch (ConnectionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

Each time you call createPartnerConnection()() you will receive the reference to newly created instance of PartnerConnection class.

Spring Jpa different object already associated

ERROR: A different object with the same identifier value was already associated with the session

Solution: See if an existing ID already exist, if not persist record. If same ID found, merge with existing record

@Override
	@Transactional(value="dwTransactionManager", propagation=Propagation.REQUIRES_NEW)
	public void insertLog(Error[] errorRecords, Long recordId, String salesforceId, String objectName) {
		for (Error errorRecord : errorRecords){
			try {
				DWLog log = new DWLog();
				log.setId(new BigDecimal(recordId));
				Calendar cal = Calendar.getInstance();
				log.setLastSyncD(cal.getTime());
				log.setLastSyncMsg("ERROR CODE: " + errorRecord.getStatusCode() + " MESSAGE: " + errorRecord.getMessage());
				log.setObjectSync(objectName);
				if (entityManager.find(DWLog.class,log.Id)!=null){
					entityManager.merge(log);	
				}else{
					entityManager.persist(log);	
				}
			} catch (Exception e) {
				logger.error("LogDaoImpl.insertLog(): " + e.getMessage());
			}
		}
	}