Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
0 Kudos
A lot of aBPM scenarios (in context of BPM and/or standalone applications), custom implementations and/or other components that are running on a PO system needs additional data (e.g. for tasks) that are not to be stored out-of-box inside the database or via BPM API. One typically reason is that an API doesn't support access to those data (like inside the BPM API for tasks/processes). Another reason can be that the release of your PO system doesn't support such access in the current version but in the next. Or the data are to granular or not allowed or to transient for a full-blown persistence/database model. Surely the aBPM framework has his own persistence layer, but also here the data will be stored more on a level of an aBPM process and not in the details of a task layer.

Inside this blog I want to show you how you can create a very flexible and generic persistence layer for all the data that you want to store with the smallest DB footprint that you ever have seen.

Before we go into implementation details first some theoretically backgrounds and requirements for an generic persistence layer:

1. Common requirements

1.1 A generic persistence approach must be very flexible and needs no information about the data that must be stored. The reason is very simple today we know only one or a small list of data that must be stored today, but requirements/application scopes will be changed and next week or the week after additional data are more relevant to store.

1.2 The initial access to this data must be fast, stable and unique.

1.3 The technically transformation from a generic interface into a persist-able format must be  encapsulated with some/a lot of service operations that represents most of the use cases.

1.4 A job that can be scheduled via NWA Java scheduler to clean up the database to hold the DB storage in a well state and removed unnecessary data from the DB.

2. Solution approach

2.1 The answer for the requirement 1.1 must be split into two parts.
First the database structure of the data part itself cannot be used multiple columns etc. If it would every change for additional fields depends an extension of the database table. The solution here is a single CLOB column (character large object). Such a database column will be manage and extended (the space inside the database) by the database itself.

Second the data inside the CLOB column should be stored in a key-value/s combination with an XML format. XML has the advantage that such content can very easy marshalled and unmarshalled into Java objects. The key-value/s approach inside the XML format helps additionally that specific data can be access inside the unmashalled Java objects via the key. Values and not a single value per key allows to store list of values for one key (e.g. in case of multiple values like the list of the last 10 used end user selections or so)
<Pairs>
<Pair>
<Key>data1</Key>
<Value>datavalue1</Value>
...
<Pair>
<Pair>
<Key>data2</Key>
<Value>datavalue1</Value>
<Value>datavalue2</Value>
...
<Pair>
...
<Pairs>

2.2 The solution for requirement 1.2 is a unique id like the BPM task instance id or a unique user name or an own generated unique identifier. For additional task data the BPM task instance id would be the best, for additional user data the unique logon name. The identifier could be realized with a string column for a primary key.

2.3 Requirement 1.3 can realized via a stateless session bean (EJB) with JEE default approaches. The implementation itself must transform the Java objects (like a DTO that separate the service from the persistence layer) into the generic XML format. The data will be stored with regular JPA entity objects and operations via Persistence Manager. Hint: If JPA could not be used, also regular SQL queries via JDBC calls are possible.

2.4 Requirement 1.4 can also be realized via a default SAP job implementation that can be found on help.sap.com Developing and Scheduling Jobs. The pre-requirement is that the service layer has some operations to read and delete some data from your generic persistence layer (e.g. read all unique ids, delete entry by unique id, etc.)

Now lets look into some implementation details (Hint: not all files will be shown, excluded are files like properties or constants, but the interesting parts are there):

DC structure

You need an Dictionary DC (for the persistence table/s), an EJB DC (for the persistence, service and data object implementation) and an EAR DC for the deployment. The structure should nearly looks like the following:





Hint: In my case the name of the EAR DC differ from the EJB and DIC DCs only whereby the fact that the EAR DC exists before and I have reused the existing DC for deployment.

Inside the EJB DCs exists some Java packages and classes that must be discovered now in detail:



DataPair and DataPairs contains the Java implementation of the XML representation for the key-value/s structure:
package <Vendor>.common.data.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "Pair")
public class DataPair implements Serializable {

private static final long serialVersionUID = 1L;

private String key;
private List<String> value = new ArrayList<String>();

public DataPair() {
super();
}

public DataPair(String key, List<String> value) {
super();
this.key = key;
this.value.addAll(value);
}

@XmlElement(name = "Key")
public String getKey() {
return key;
}

public void setKey(String key) {
this.key = key;
}

@XmlElement(name = "Value")
public List<String> getValue() {
return value;
}

public void setValue(List<String> value) {
this.value = value;
}

}

package <Vendor>.common.data.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "Pairs")
public class DataPairs implements Serializable {

private static final long serialVersionUID = 1L;

private List<DataPair> dataPair;

public DataPairs() {
dataPair = new ArrayList<DataPair>();
}

@XmlElement(name = "Pair")
public List<DataPair> getDataPair() {
return dataPair;
}

public void setDataPair(List<DataPair> dataPair) {
this.dataPair = dataPair;
}

public void add(DataPair dataPair) {
this.dataPair.add(dataPair);
}

public void addAll(List<DataPair> dataPair) {
this.dataPair.addAll(dataPair);
}

public void remove(DataPair dataPair) {
this.dataPair.remove(dataPair);
}

public void removeAll(List<DataPair> dataPair) {
this.dataPair.removeAll(dataPair);
}
}

The classes contains default getter and setter operations to work on there content.

TaskDataDto contains the specific structure for the TaskDataService layer. The structure represents the view that is visible from outside:
package <Vendor>.common.data.model;

import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

import com.sap.tc.logging.Location;
import com.sap.tc.logging.Severity;
import com.sap.tc.logging.SimpleLogger;

public class TaskDataDto implements Serializable {

private static final long serialVersionUID = 1L;
private static Location logger = Location.getLocation(TaskDataDto.class);

private String taskInstanceID;
private DataPairs taskDataPairs;

public TaskDataDto() {
super();
}

public TaskDataDto(TaskDataEntry taskDataEntry) {
super();
if (null != taskDataEntry) {
this.setTaskInstanceID(taskDataEntry.getTaskInstanceID());
this.setTaskDataPairs(taskDataEntry.getContent());
}
}

public String getTaskInstanceID() {
return taskInstanceID;
}

public void setTaskInstanceID(String taskInstanceID) {
this.taskInstanceID = taskInstanceID;
}

public DataPairs getTaskDataPairs() {
return taskDataPairs;
}

public void setTaskDataPairs(DataPairs taskDataPairs) {
this.taskDataPairs = taskDataPairs;
}

private void setTaskDataPairs(String content) {
try {
taskDataPairs = unmarshallContent(content);
} catch (JAXBException e) {
taskDataPairs = null;
SimpleLogger.trace(Severity.INFO, logger, "An error occured:", e);
}
}

public TaskDataEntry toEntity() throws JAXBException {
TaskDataEntry taskDataEntry = new TaskDataEntry();
taskDataEntry.setTaskInstanceID(taskInstanceID);
taskDataEntry.setContent(marshallTaskDataPairs(taskDataPairs));
return taskDataEntry;
}

public static String marshallTaskDataPairs(DataPairs taskDataPairs) throws JAXBException {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(DataPairs.class);
Marshaller marshaller = jaxbContext.createMarshaller();
StringWriter strWriter = new StringWriter();
marshaller.marshal(taskDataPairs, strWriter);
return strWriter.toString();
} catch (JAXBException e) {
SimpleLogger.traceThrowable(Severity.ERROR, logger, "JAXBException inside toEntity()", e);
throw e;
}
}

public static DataPairs unmarshallContent(String content) throws JAXBException {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(DataPairs.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
StringReader strReader = new StringReader(content);
return (DataPairs) unmarshaller.unmarshal(strReader);
} catch (JAXBException e) {
SimpleLogger.traceThrowable(Severity.ERROR, logger, "JAXBException inside setTaskDataPairs()", e);
throw e;
}
}

}

TaskDataEntry is the class for the JPA entity object:
package <vendor>.common.data.model;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Table;

@Entity
@Table(name = "<DB prefix>_TASK_DATA")
public class TaskDataEntry implements Serializable {
private static final long serialVersionUID = 1L;

@Id
@Column(name = "TID", length = 50)
private String taskInstanceID;

@Lob
@Column(name = "CONTENT")
private String content;

public TaskDataEntry() {
super();
}

public String getTaskInstanceID() {
return this.taskInstanceID;
}

public void setTaskInstanceID(String taskInstanceID) {
this.taskInstanceID = taskInstanceID;
}

public String getContent() {
return this.content;
}

public void setContent(String content) {
this.content = content;
}

@Override
public boolean equals(Object pObj) {
return (pObj != null) && pObj.getClass().equals(this.getClass()) && (this.getTaskInstanceID() != null) && (((TaskDataEntry) pObj).getTaskInstanceID() != null)
&& this.getTaskInstanceID().equals(((TaskDataEntry) pObj).getTaskInstanceID());
}

@Override
public int hashCode() {
if (getTaskInstanceID() != null) {
return getTaskInstanceID().hashCode();
} else {
return super.hashCode();
}
}
}

The TaskDataService contains the implementation of the corresponding interface, that used JPA queries to create, update or delete the additional task data inside the generic persistence table.
package <Vendor>.common.data.service;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TransactionRequiredException;
import javax.xml.bind.JAXBException;

import <Vendor>.common.data.model.DataPair;
import <Vendor>.common.data.model.DataPairs;
import <Vendor>.common.data.model.TaskDataConstants;
import <Vendor>.common.data.model.TaskDataDto;
import <Vendor>.common.data.model.TaskDataEntry;
import com.sap.tc.logging.Location;
import com.sap.tc.logging.Severity;
import com.sap.tc.logging.SimpleLogger;

/**
* Session Bean implementation class TaskDataService
*/
@Stateless
public class TaskDataService implements TaskDataServiceLocal {

private Location logger = Location.getLocation(TaskDataService.class);

private static final String TASK_INSTANCE_ID = "taskInstanceID";
private static final String TID_NULL_ERROR = "TaskInstanceID must not be null";
private static final String TIDS_NULL_ERROR = "TaskInstanceIDs must not be null";
private static final String DTO_NULL_ERROR = "TaskDataDto must not be null";
private static final String KEY_NULL_ERROR = "Key must not be null";
private static final String KEYS_NULL_ERROR = "Keys must not be null";
private static final String VALUE_NULL_ERROR = "Value must not be null";
private static final String DATAMAP_NULL_ERROR = "TaskDataMap must not be null";
private static final String STANDARD_ERROR = "An error occured:";
private static final String SQL_DELETE_ALL_TASK_DATA = "delete from TaskDataEntry t";
private static final String SQL_DELETE_TASK_DATA_BY_TID = "delete from TaskDataEntry t where t.taskInstanceID = :taskInstanceID";
private static final String SQL_DELETE_TASK_DATA_BY_TID_LIST = "delete from TaskDataEntry t where t.taskInstanceID in ({0})";
private static final String SQL_READ_ALL_TASK_DATA = "select t from TaskDataEntry t";
private static final String SQL_READ_TASK_DATA_BY_TID = "select t from TaskDataEntry t where t.taskInstanceID = :taskInstanceID";
private static final String SQL_READ_TASK_DATA_BY_TID_LIST = "select t from TaskDataEntry t where t.taskInstanceID in ({0})";
private static final String SQL_VALUE_WITH_COMMA = "\'\'{0}\'\'";
private static final String SQL_COMMA_VALUE_WITH_COMMA = ",\'\'{0}\'\'";

@PersistenceContext
private EntityManager em;

private TaskDataDto createTaskData(TaskDataDto taskDataDto) throws TaskDataServiceException {
if (taskDataDto == null) {
throw new TaskDataServiceException(DTO_NULL_ERROR);
}
try {
return new TaskDataDto(em.merge(taskDataDto.toEntity()));
} catch (IllegalStateException e) {
throw new TaskDataServiceException(e);
} catch (IllegalArgumentException e) {
throw new TaskDataServiceException(e);
} catch (TransactionRequiredException e) {
throw new TaskDataServiceException(e);
} catch (JAXBException e) {
throw new TaskDataServiceException(e);
}
}

@Override
public boolean deleteAllTaskData() throws TaskDataServiceException {
Query query = em.createQuery(SQL_DELETE_ALL_TASK_DATA);
try {
query.executeUpdate();
return true;
} catch (IllegalStateException e) {
throw new TaskDataServiceException(e);
}
}

@Override
public boolean deleteTaskDataByTaskInstanceID(String taskInstanceID) throws TaskDataServiceException {
if (taskInstanceID == null) {
throw new TaskDataServiceException(TID_NULL_ERROR);
}
Query query = em.createQuery(SQL_DELETE_TASK_DATA_BY_TID);
query.setParameter(TASK_INSTANCE_ID, taskInstanceID);
try {
query.executeUpdate();
return true;
} catch (IllegalStateException e) {
throw new TaskDataServiceException(e);
}
}

@Override
public List<TaskDataDto> readAllTaskData() {
Query query = em.createQuery(SQL_READ_ALL_TASK_DATA);
List<TaskDataEntry> resultList = query.getResultList();
return createDtoList(resultList);
}

@Override
public TaskDataDto readTaskDataByTaskInstanceID(String taskInstanceID) throws TaskDataServiceException {
if (taskInstanceID == null) {
throw new TaskDataServiceException(TID_NULL_ERROR);
}
Query query = em.createQuery(SQL_READ_TASK_DATA_BY_TID);
query.setParameter(TASK_INSTANCE_ID, taskInstanceID);
TaskDataEntry taskDataEntry = null;
try {
taskDataEntry = (TaskDataEntry) query.getSingleResult();
} catch (NoResultException e) {
// please return null in case of no result
SimpleLogger.trace(Severity.INFO, logger, STANDARD_ERROR, e);
}
return taskDataEntry != null ? new TaskDataDto(taskDataEntry) : null;

}

@Override
public TaskDataDto createAndUpdateTaskData(TaskDataDto taskDataDto) throws TaskDataServiceException {
if (taskDataDto == null) {
throw new TaskDataServiceException(DTO_NULL_ERROR);
}
try {
Query query = em.createQuery(SQL_READ_TASK_DATA_BY_TID);
query.setParameter(TASK_INSTANCE_ID, taskDataDto.getTaskInstanceID());
TaskDataEntry taskDataEntry = null;
try {
taskDataEntry = (TaskDataEntry) query.getSingleResult();
taskDataEntry.setContent(TaskDataDto.marshallTaskDataPairs(taskDataDto.getTaskDataPairs()));
return new TaskDataDto(em.merge(taskDataEntry));
} catch (NoResultException e) {
// in case the entry doesn't exists create a new one
SimpleLogger.trace(Severity.INFO, logger, STANDARD_ERROR, e);
return createTaskData(taskDataDto);
}
} catch (IllegalStateException e) {
throw new TaskDataServiceException(e);
} catch (IllegalArgumentException e) {
throw new TaskDataServiceException(e);
} catch (TransactionRequiredException e) {
throw new TaskDataServiceException(e);
} catch (JAXBException e) {
throw new TaskDataServiceException(e);
}
}

private List<TaskDataDto> createDtoList(List<TaskDataEntry> resultList) {
List<TaskDataDto> dtoList = new ArrayList<TaskDataDto>();
if (resultList != null) {
for (TaskDataEntry aTaskDataEntry : resultList) {
dtoList.add(new TaskDataDto(aTaskDataEntry));
}
}
return dtoList;
}

@Override
public List<String> readAllTaskInstanceIDsOfTaskData() {
Query query = em.createQuery(SQL_READ_ALL_TASK_DATA);
List<TaskDataEntry> taskDataEntries = query.getResultList();
List<String> taskInstanceIDs = new ArrayList<String>();
for (TaskDataEntry aTaskDataEntry : taskDataEntries) {
taskInstanceIDs.add(aTaskDataEntry.getTaskInstanceID());
}
return taskInstanceIDs;
}

@Override
public Map<String, List<String>> readTaskDataHashMapByTaskInstanceID(String taskInstanceID) throws TaskDataServiceException {
if (taskInstanceID == null) {
throw new TaskDataServiceException(TID_NULL_ERROR);
}
HashMap<String, List<String>> taskDataMap = new HashMap<String, List<String>>();
TaskDataDto taskDataDto = readTaskDataByTaskInstanceID(taskInstanceID);
if (null != taskDataDto && null != taskDataDto.getTaskDataPairs() && null != taskDataDto.getTaskDataPairs().getDataPair()) {
taskDataMap = taskDataPairToHashMap(taskDataDto.getTaskDataPairs().getDataPair());
}
return taskDataMap;
}

@Override
public List<String> readTaskDataListByTaskInstanceIDAndKey(String taskInstanceID, String key) throws TaskDataServiceException {
if (taskInstanceID == null) {
throw new TaskDataServiceException(TID_NULL_ERROR);
}
if (key == null) {
throw new TaskDataServiceException(KEY_NULL_ERROR);
}
List<String> taskInstanceIDs = new ArrayList<String>();
taskInstanceIDs.add(taskInstanceID);
Map<String, List<String>> map = readTaskDataValueHashMapByTaskInstanceIDsAndKey(taskInstanceIDs, key);
return map.get(taskInstanceID);
}

private TaskDataDto createTaskDataByTaskInstanceIDAndHashMap(String taskInstanceID, Map<String, List<String>> taskDataMap) throws TaskDataServiceException {
if (taskInstanceID == null) {
throw new TaskDataServiceException(TID_NULL_ERROR);
}
if (taskDataMap == null) {
throw new TaskDataServiceException(DATAMAP_NULL_ERROR);
}
TaskDataDto taskDataDto = new TaskDataDto();
taskDataDto.setTaskInstanceID(taskInstanceID);
DataPairs pairs = new DataPairs();
Set<String> keys = taskDataMap.keySet();
for (String aKey : keys) {
DataPair aPair = new DataPair(aKey, taskDataMap.get(aKey));
pairs.add(aPair);
}
taskDataDto.setTaskDataPairs(pairs);
return createTaskData(taskDataDto);
}

@Override
public TaskDataDto createAndUpdateTaskDataByTaskInstanceIDAndHashMap(String taskInstanceID, Map<String, List<String>> taskDataMap) throws TaskDataServiceException {
if (taskInstanceID == null) {
throw new TaskDataServiceException(TID_NULL_ERROR);
}
if (taskDataMap == null) {
throw new TaskDataServiceException(DATAMAP_NULL_ERROR);
}
try {
Query query = em.createQuery(SQL_READ_TASK_DATA_BY_TID);
query.setParameter(TASK_INSTANCE_ID, taskInstanceID);
TaskDataEntry taskDataEntry = null;
try {
// get the task data
taskDataEntry = (TaskDataEntry) query.getSingleResult();
DataPairs entryTaskDataPairs = TaskDataDto.unmarshallContent(taskDataEntry.getContent());
List<DataPair> entryTaskDataPair = entryTaskDataPairs.getDataPair();
HashMap<String, List<String>> entryHashMap = taskDataPairToHashMap(entryTaskDataPair);
// modify the task data
Set<String> inputHashMapKeys = taskDataMap.keySet();
for (String aInputHashMapKey : inputHashMapKeys) {
entryHashMap.put(aInputHashMapKey, taskDataMap.get(aInputHashMapKey));
}
// store the modified task data
entryTaskDataPair = hashMapToTaskDataPair(entryHashMap);
entryTaskDataPairs.setDataPair(entryTaskDataPair);
taskDataEntry.setContent(TaskDataDto.marshallTaskDataPairs(entryTaskDataPairs));
return new TaskDataDto(em.merge(taskDataEntry));
} catch (NoResultException e) {
// in case the entry doesn't exists create a new one
SimpleLogger.trace(Severity.INFO, logger, STANDARD_ERROR, e);
return createTaskDataByTaskInstanceIDAndHashMap(taskInstanceID, taskDataMap);
}
} catch (IllegalStateException e) {
throw new TaskDataServiceException(e);
} catch (IllegalArgumentException e) {
throw new TaskDataServiceException(e);
} catch (TransactionRequiredException e) {
throw new TaskDataServiceException(e);
} catch (JAXBException e) {
throw new TaskDataServiceException(e);
}
}

private HashMap<String, List<String>> taskDataPairToHashMap(List<DataPair> taskDataPair) {
HashMap<String, List<String>> taskDataMap = new HashMap<String, List<String>>();
for (DataPair aTaskDataPair : taskDataPair) {
taskDataMap.put(aTaskDataPair.getKey(), aTaskDataPair.getValue());
}
return taskDataMap;
}

private List<DataPair> hashMapToTaskDataPair(HashMap<String, List<String>> hashMap) {
List<DataPair> taskDataPair = new ArrayList<DataPair>();
Set<String> keys = hashMap.keySet();
for (String aKey : keys) {
DataPair aTaskDataPair = new DataPair();
aTaskDataPair.setKey(aKey);
aTaskDataPair.setValue(hashMap.get(aKey));
taskDataPair.add(aTaskDataPair);
}
return taskDataPair;
}

@Override
public TaskDataDto createAndUpdateTaskDataByTaskInstanceIDAndKey(String taskInstanceID, String key, List<String> value) throws TaskDataServiceException {
if (taskInstanceID == null) {
throw new TaskDataServiceException(TID_NULL_ERROR);
}
if (key == null) {
throw new TaskDataServiceException(KEY_NULL_ERROR);
}
if (value == null) {
throw new TaskDataServiceException(VALUE_NULL_ERROR);
}
HashMap<String, List<String>> taskDataMap = new HashMap<String, List<String>>();
taskDataMap.put(key, value);
return createAndUpdateTaskDataByTaskInstanceIDAndHashMap(taskInstanceID, taskDataMap);
}

@Override
public TaskDataDto createAndUpdateTaskDataByTaskInstanceIDAndKey(String taskInstanceID, TaskDataConstants key, List<String> value) throws TaskDataServiceException {
return createAndUpdateTaskDataByTaskInstanceIDAndKey(taskInstanceID, key.toString(), value);
}

@Override
public TaskDataDto deleteTaskDataByTaskInstanceIDAndKey(String taskInstanceID, String key) throws TaskDataServiceException {
if (taskInstanceID == null) {
throw new TaskDataServiceException(TID_NULL_ERROR);
}
if (key == null) {
throw new TaskDataServiceException(KEY_NULL_ERROR);
}
try {
Query query = em.createQuery(SQL_READ_TASK_DATA_BY_TID);
query.setParameter(TASK_INSTANCE_ID, taskInstanceID);
TaskDataEntry taskDataEntry = null;
try {
// get the task data
taskDataEntry = (TaskDataEntry) query.getSingleResult();
DataPairs entryTaskDataPairs = TaskDataDto.unmarshallContent(taskDataEntry.getContent());
List<DataPair> entryTaskDataPair = entryTaskDataPairs.getDataPair();
HashMap<String, List<String>> entryHashMap = taskDataPairToHashMap(entryTaskDataPair);
// remove the specific task data
entryHashMap.remove(key);
// store the modified task data
entryTaskDataPair = hashMapToTaskDataPair(entryHashMap);
entryTaskDataPairs.setDataPair(entryTaskDataPair);
taskDataEntry.setContent(TaskDataDto.marshallTaskDataPairs(entryTaskDataPairs));
return new TaskDataDto(em.merge(taskDataEntry));
} catch (NoResultException e) {
// in case the entry doesn't exists do nothing
return null;
}
} catch (IllegalStateException e) {
throw new TaskDataServiceException(e);
} catch (IllegalArgumentException e) {
throw new TaskDataServiceException(e);
} catch (TransactionRequiredException e) {
throw new TaskDataServiceException(e);
} catch (JAXBException e) {
throw new TaskDataServiceException(e);
}
}

@Override
public TaskDataDto deleteTaskDataByTaskInstanceIDAndKey(String taskInstanceID, TaskDataConstants key) throws TaskDataServiceException {
return deleteTaskDataByTaskInstanceIDAndKey(taskInstanceID, key.toString());
}

@Override
public TaskDataDto deleteTaskDataByTaskInstanceIDAndKeyList(String taskInstanceID, List<String> keys) throws TaskDataServiceException {
if (taskInstanceID == null) {
throw new TaskDataServiceException(TID_NULL_ERROR);
}
if (keys == null) {
throw new TaskDataServiceException(KEYS_NULL_ERROR);
}
try {
Query query = em.createQuery(SQL_READ_TASK_DATA_BY_TID);
query.setParameter(TASK_INSTANCE_ID, taskInstanceID);
TaskDataEntry taskDataEntry = null;
try {
// get the task data
taskDataEntry = (TaskDataEntry) query.getSingleResult();
DataPairs entryTaskDataPairs = TaskDataDto.unmarshallContent(taskDataEntry.getContent());
List<DataPair> entryTaskDataPair = entryTaskDataPairs.getDataPair();
HashMap<String, List<String>> entryHashMap = taskDataPairToHashMap(entryTaskDataPair);
// remove the specific task data
for (String aKey : keys) {
entryHashMap.remove(aKey);
}
// store the modified task data
entryTaskDataPair = hashMapToTaskDataPair(entryHashMap);
entryTaskDataPairs.setDataPair(entryTaskDataPair);
taskDataEntry.setContent(TaskDataDto.marshallTaskDataPairs(entryTaskDataPairs));
return new TaskDataDto(em.merge(taskDataEntry));
} catch (NoResultException e) {
// in case the entry doesn't exists do nothing
return null;
}
} catch (IllegalStateException e) {
throw new TaskDataServiceException(e);
} catch (IllegalArgumentException e) {
throw new TaskDataServiceException(e);
} catch (TransactionRequiredException e) {
throw new TaskDataServiceException(e);
} catch (JAXBException e) {
throw new TaskDataServiceException(e);
}
}

@Override
public TaskDataDto deleteTaskDataByTaskInstanceIDAndKeyConstants(String taskInstanceID, List<TaskDataConstants> keys) throws TaskDataServiceException {
List<String> stringKeys = new ArrayList<String>();
for (TaskDataConstants aTaskDataConstant : keys) {
stringKeys.add(aTaskDataConstant.toString());
}
return deleteTaskDataByTaskInstanceIDAndKeyList(taskInstanceID, stringKeys);
}

@Override
public List<String> readTaskDataKeyListByTaskInstanceID(String taskInstanceID) throws TaskDataServiceException {
if (taskInstanceID == null) {
throw new TaskDataServiceException(TID_NULL_ERROR);
}
List<String> taskDataKeys = new ArrayList<String>();
try {
Query query = em.createQuery(SQL_READ_TASK_DATA_BY_TID);
query.setParameter(TASK_INSTANCE_ID, taskInstanceID);
TaskDataEntry taskDataEntry = null;
try {
// get the task data
taskDataEntry = (TaskDataEntry) query.getSingleResult();
DataPairs entryTaskDataPairs = TaskDataDto.unmarshallContent(taskDataEntry.getContent());
List<DataPair> entryTaskDataPair = entryTaskDataPairs.getDataPair();
HashMap<String, List<String>> entryHashMap = taskDataPairToHashMap(entryTaskDataPair);
taskDataKeys.addAll(entryHashMap.keySet());
} catch (NoResultException e) {
// in case the entry doesn't exists do nothing
SimpleLogger.trace(Severity.INFO, logger, STANDARD_ERROR, e);
}
} catch (IllegalStateException e) {
SimpleLogger.trace(Severity.INFO, logger, STANDARD_ERROR, e);
} catch (IllegalArgumentException e) {
SimpleLogger.trace(Severity.INFO, logger, STANDARD_ERROR, e);
} catch (TransactionRequiredException e) {
SimpleLogger.trace(Severity.INFO, logger, STANDARD_ERROR, e);
} catch (JAXBException e) {
SimpleLogger.trace(Severity.INFO, logger, STANDARD_ERROR, e);
}
return taskDataKeys;
}

@Override
public boolean hasTaskDataByTaskInstanceID(String taskInstanceID) throws TaskDataServiceException {
if (taskInstanceID == null) {
throw new TaskDataServiceException(TID_NULL_ERROR);
}
boolean hasTaskData = false;
List<String> taskDataKeys = readTaskDataKeyListByTaskInstanceID(taskInstanceID);
if (!taskDataKeys.isEmpty()) {
hasTaskData = true;
}
return hasTaskData;
}

@Override
public Map<String, List<String>> readTaskDataValueHashMapByTaskInstanceIDsAndKey(List<String> taskInstanceIDs, String key) throws TaskDataServiceException {
if (taskInstanceIDs == null) {
throw new TaskDataServiceException(TIDS_NULL_ERROR);
}
if (key == null) {
throw new TaskDataServiceException(KEY_NULL_ERROR);
}
Map<String, List<String>> taskDatasMap = new HashMap<String, List<String>>();
if (!taskInstanceIDs.isEmpty()) {
StringBuilder in = new StringBuilder(MessageFormat.format(SQL_VALUE_WITH_COMMA, taskInstanceIDs.get(0).toLowerCase()));
for (int i = 1; i < taskInstanceIDs.size(); i++) {
in.append(MessageFormat.format(SQL_COMMA_VALUE_WITH_COMMA, taskInstanceIDs.get(i).toLowerCase()));
}
String jpql = MessageFormat.format(SQL_READ_TASK_DATA_BY_TID_LIST, in);
SimpleLogger.trace(Severity.DEBUG, logger, jpql);
Query query = em.createQuery(jpql);
List<TaskDataEntry> resultList = query.getResultList();
for (TaskDataEntry taskDataEntry : resultList) {
try {

DataPairs entryTaskDataPairs;
entryTaskDataPairs = TaskDataDto.unmarshallContent(taskDataEntry.getContent());
List<DataPair> entryTaskDataPair = entryTaskDataPairs.getDataPair();
Map<String, List<String>> taskDataMap = taskDataPairToHashMap(entryTaskDataPair);
taskDatasMap.put(taskDataEntry.getTaskInstanceID(), (taskDataMap.containsKey(key)) ? taskDataMap.get(key) : null);
} catch (JAXBException e) {
SimpleLogger.trace(Severity.INFO, logger, STANDARD_ERROR, e);
}
}
}
return taskDatasMap;
}

@Override
public Map<String, List<String>> readTaskDataValueHashMapByTaskInstanceIDsAndKey(List<String> taskInstanceIDs, TaskDataConstants key) throws TaskDataServiceException {
if (taskInstanceIDs == null) {
throw new TaskDataServiceException(TIDS_NULL_ERROR);
}
if (key == null) {
throw new TaskDataServiceException(KEY_NULL_ERROR);
}
return readTaskDataValueHashMapByTaskInstanceIDsAndKey(taskInstanceIDs, key.toString());
}

@Override
public Map<String, Map<String, List<String>>> readTaskDataHashMapByTaskInstanceIDs(List<String> taskInstanceIDs) throws TaskDataServiceException {
if (taskInstanceIDs == null) {
throw new TaskDataServiceException(TIDS_NULL_ERROR);
}
Map<String, Map<String, List<String>>> taskDatasMap = new HashMap<String, Map<String, List<String>>>();

StringBuilder in = new StringBuilder(MessageFormat.format(SQL_VALUE_WITH_COMMA, taskInstanceIDs.get(0).toLowerCase()));
for (int i = 1; i < taskInstanceIDs.size(); i++) {
in.append(MessageFormat.format(SQL_COMMA_VALUE_WITH_COMMA, taskInstanceIDs.get(i).toLowerCase()));
}
String jpql = MessageFormat.format(SQL_READ_TASK_DATA_BY_TID_LIST, in);
SimpleLogger.trace(Severity.DEBUG, logger, jpql);
Query query = em.createQuery(jpql);
List<TaskDataEntry> resultList = query.getResultList();

for (TaskDataEntry taskDataEntry : resultList) {
try {
Map<String, List<String>> taskDataMap = new HashMap<String, List<String>>();
DataPairs entryTaskDataPairs;

entryTaskDataPairs = TaskDataDto.unmarshallContent(taskDataEntry.getContent());

List<DataPair> entryTaskDataPair = entryTaskDataPairs.getDataPair();
taskDataMap = taskDataPairToHashMap(entryTaskDataPair);
taskDatasMap.put(taskDataEntry.getTaskInstanceID(), taskDataMap);
} catch (JAXBException e) {
SimpleLogger.trace(Severity.INFO, logger, STANDARD_ERROR, e);
}
}
return taskDatasMap;
}

@Override
public boolean deleteTaskDataByTaskInstanceIDs(List<String> taskInstanceIDs) throws TaskDataServiceException {
if (taskInstanceIDs == null) {
throw new TaskDataServiceException(TIDS_NULL_ERROR);
}
StringBuilder in = new StringBuilder(MessageFormat.format(SQL_VALUE_WITH_COMMA, taskInstanceIDs.get(0).toLowerCase()));
for (int i = 1; i < taskInstanceIDs.size(); i++) {
in.append(MessageFormat.format(SQL_COMMA_VALUE_WITH_COMMA, taskInstanceIDs.get(i).toLowerCase()));
}
String jpql = MessageFormat.format(SQL_DELETE_TASK_DATA_BY_TID_LIST, in);
SimpleLogger.trace(Severity.DEBUG, logger, jpql);
Query query = em.createQuery(jpql);
try {
query.executeUpdate();
return true;
} catch (IllegalStateException e) {
throw new TaskDataServiceException(e);
}
}
}

After building and deploying the DCs the service layer can be used inside aBPM scenario and/or other Java implementations. For an easy test the service layer can also be executed inside EJB Explorer and the result can be checked via SQL Data Browser: