/* * Copyright 2015 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jbpm.process.workitem.jpa; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import javax.persistence.Query; import org.jbpm.process.workitem.AbstractLogOrThrowWorkItemHandler; import org.kie.api.runtime.process.WorkItem; import org.kie.api.runtime.process.WorkItemManager; import org.kie.internal.runtime.Cacheable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * A WorkItemHandler to perform JPA operations. <br /> * An <b>Action</b> must be provided as an input parameter. The supported value * for the Action parameters: * <ul> * <li>Create: Persist the object and return the attached entity. TO persist an * object you must provide the object using the WIH parameter <b>Entity</b></li> * <li>Update: Update an attached entity and returns the updated entity. You * must provide the attached entity using the <b>Entity</b> WIH parameter.</li> * <li>Delete: Delete an attached entity. You must provide the attached entity * using the <b>Entity</b> WIH parameter.</li> * <li>Get: Get an entity by ID. You must provide the return type using the * <b>Type</b> parameter with the FQN of the target class and the ID using the * <b>Id</b> parameter.</li> * <li>Query: Executes a named query and return the list of results (if any). * The query must be provided using the <b>Query</b> input parameter and you may * provide query parameters in the form of a Map with key String and value * Object using the <b>QueryParameters</b> WIH input parameter. The result of * the query is put on the output parameter <b>QueryResults</b>.</li> * </ul> * When registering the WIH in the deployment descriptor, you must provide the * classloader where your mapped entities are and the name of the persistence * unit you configured in <em>persistence.xml</em>. * */ public class JPAWorkItemHandler extends AbstractLogOrThrowWorkItemHandler implements Cacheable { private static final Logger logger = LoggerFactory .getLogger(JPAWorkItemHandler.class); public static final String P_RESULT = "Result"; public static final String P_TYPE = "Type"; public static final String P_ID = "Id"; public static final String P_ENTITY = "Entity"; public static final String P_ACTION = "Action"; public static final String P_QUERY = "Query"; public static final String P_QUERY_PARAMS = "QueryParameters"; public static final String P_QUERY_RESULTS = "QueryResults"; public static final String CREATE_ACTION = "CREATE"; public static final String UPDATE_ACTION = "UPDATE"; public static final String GET_ACTION = "GET"; public static final String DELETE_ACTION = "DELETE"; public static final String QUERY_ACTION = "QUERY"; private EntityManagerFactory emf; private ClassLoader classloader; public JPAWorkItemHandler(String persistenceUnit, ClassLoader cl) { setLogThrownException(true); ClassLoader tccl = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(cl); this.emf = Persistence.createEntityManagerFactory(persistenceUnit); } finally { Thread.currentThread().setContextClassLoader(tccl); } this.classloader = cl; } public void executeWorkItem(WorkItem wi, WorkItemManager wim) { Object actionParam = wi.getParameter(P_ACTION); Object entity = wi.getParameter(P_ENTITY); Object id = wi.getParameter(P_ID); Object type = wi.getParameter(P_TYPE); Object queryName = wi.getParameter(P_QUERY); Object queryParams = wi.getParameter(P_QUERY_PARAMS); Map<String, Object> params = new HashMap<String, Object>(); List<Object> queryResults = Collections.emptyList(); String action; if (actionParam == null) { throw new IllegalArgumentException( "An action is required. Use 'delete', 'create', 'update', query or 'get'"); } // Only QUERY does no require an entity parameter if (entity == null && P_ACTION.equals(QUERY_ACTION)) { throw new IllegalArgumentException( "An entity is required. Use the 'entity' parameter"); } action = String.valueOf(actionParam).trim().toUpperCase(); logger.debug("Action {} on {}", action, entity); EntityManager em = emf.createEntityManager(); try { // join the process transaction em.joinTransaction(); switch (action) { case DELETE_ACTION: doDelete(em, entity, id); break; case GET_ACTION: if (id == null || type == null) { throw new IllegalArgumentException( "Id or type can't be null when getting an entity"); } // only works with long for now entity = doGet(em, type.toString(), Long.parseLong(id.toString())); break; case UPDATE_ACTION: doUpdate(em, entity); break; case CREATE_ACTION: em.persist(entity); break; case QUERY_ACTION: if (queryName == null) { throw new IllegalArgumentException("You must provide a '" + P_QUERY + "' parameter to run named queries."); } queryResults = doQuery(em, String.valueOf(queryName), queryParams); break; default: throw new IllegalArgumentException( "Action " + action+ " not recognized. Use 'delete', 'create', 'update', query, or 'get'"); } } catch (Exception e) { logger.debug("Error performing JPA action ", e); throw e; } finally { em.close(); } params.put(P_RESULT, entity); params.put(P_QUERY_RESULTS, queryResults); wim.completeWorkItem(wi.getId(), params); } @SuppressWarnings("unchecked") private List<Object> doQuery(EntityManager em, String queryName, Object queryParams) { logger.debug("About to run query {}", queryName); Map<String, Object> params; Query namedQuery = em.createQuery(queryName); if (queryParams == null) { logger.debug("No parameters were provided"); } else { params = ((Map<String, Object>) queryParams); logger.debug("Parameters {}", params); params.forEach(namedQuery::setParameter); } return namedQuery.getResultList(); } private Object doUpdate(EntityManager em, Object entity) { return em.merge(entity); } private Object doGet(EntityManager em, String clazz, Object id) { Class<?> type = loadClass(clazz); return em.find(type, id); } private void doDelete(EntityManager em, Object entity, Object id) { // detached entity if(!em.contains(entity)) { entity = doGet(em, entity.getClass().getName(), id); } if(entity == null) { throw new IllegalArgumentException("Can't load the entity to remove. Provide an attached entity or the id to load it."); } em.remove(entity); } public void close() { emf.close(); } public void abortWorkItem(WorkItem wi, WorkItemManager wim) { wim.abortWorkItem(wi.getId()); } private Class<?> loadClass(String clazz) { try { return Class.forName(clazz, false, classloader); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Can't load type " + clazz); } } }