2 * This document is a part of the source code and related artifacts
3 * for CollectionSpace, an open source collections management system
4 * for museums and related institutions:
6 * http://www.collectionspace.org
7 * http://wiki.collectionspace.org
9 * Copyright 2009 University of California at Berkeley
11 * Licensed under the Educational Community License (ECL), Version 2.0.
12 * You may not use this file except in compliance with this License.
14 * You may obtain a copy of the ECL 2.0 License at
16 * https://source.collectionspace.org/collection-space/LICENSE.txt
18 package org.collectionspace.services.common.storage.jpa;
20 import java.util.Date;
21 import java.util.List;
22 import javax.persistence.RollbackException;
23 import java.sql.BatchUpdateException;
25 import javax.persistence.EntityManager;
26 import javax.persistence.EntityManagerFactory;
27 import javax.persistence.Query;
29 import org.collectionspace.services.common.document.BadRequestException;
30 import org.collectionspace.services.common.document.DocumentException;
31 import org.collectionspace.services.common.document.DocumentFilter;
32 import org.collectionspace.services.common.document.DocumentHandler;
33 import org.collectionspace.services.common.document.DocumentNotFoundException;
34 import org.collectionspace.services.common.document.DocumentHandler.Action;
35 import org.collectionspace.services.common.document.DocumentWrapper;
36 import org.collectionspace.services.common.document.DocumentWrapperImpl;
37 import org.collectionspace.services.common.document.JaxbUtils;
39 import org.collectionspace.services.common.storage.StorageClient;
40 import org.collectionspace.services.common.context.ServiceContextProperties;
41 import org.collectionspace.services.common.context.ServiceContext;
42 import org.collectionspace.services.common.query.QueryContext;
43 import org.collectionspace.services.lifecycle.TransitionDef;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
49 * JpaStorageClient is used to perform CRUD operations on SQL storage using JPA.
50 * It uses @see DocumentHandler as IOHandler with the client.
51 * All the operations in this client are carried out under their own transactions.
52 * A call to any method would start and commit/rollback a transaction.
54 * Assumption: each persistent entityReceived has the following 3 attributes
55 <xs:element name="createdAt" type="xs:dateTime">
59 <orm:column name="created_at" nullable="false"/>
64 <xs:element name="updatedAt" type="xs:dateTime">
68 <orm:column name="updated_at" />
74 <xs:attribute name="csid" type="xs:string">
78 <orm:column name="csid" length="128" nullable="false"/>
84 * $LastChangedRevision: $ $LastChangedDate: $
86 public class JpaStorageClientImpl implements StorageClient {
89 private final Logger logger = LoggerFactory.getLogger(JpaStorageClientImpl.class);
92 * Instantiates a new jpa storage client.
94 public JpaStorageClientImpl() {
99 * @see org.collectionspace.services.common.storage.StorageClient#create(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
102 public String create(ServiceContext ctx,
103 DocumentHandler handler) throws BadRequestException,
105 boolean rollbackTransaction = false;
107 throw new IllegalArgumentException(
108 "create: ctx is missing");
110 if (handler == null) {
111 throw new IllegalArgumentException(
112 "create: handler is missing");
114 EntityManagerFactory emf = null;
115 EntityManager em = null;
117 handler.prepare(Action.CREATE);
118 Object entity = handler.getCommonPart();
119 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entity);
120 handler.handle(Action.CREATE, wrapDoc);
121 JaxbUtils.setValue(entity, "setCreatedAtItem", Date.class, new Date());
122 emf = JpaStorageUtils.getEntityManagerFactory();
123 em = emf.createEntityManager();
124 em.getTransaction().begin(); { //begin of transaction block
127 em.getTransaction().commit();
128 handler.complete(Action.CREATE, wrapDoc);
129 return (String) JaxbUtils.getValue(entity, "getCsid");
130 } catch (BadRequestException bre) {
131 rollbackTransaction = true;
133 } catch (DocumentException de) {
134 rollbackTransaction = true;
136 } catch (Exception e) {
137 rollbackTransaction = true;
138 if (logger.isDebugEnabled()) {
139 logger.debug("Caught exception ", e);
141 throw DocumentException.createDocumentException(e);
144 if (rollbackTransaction == true) {
145 if (em.getTransaction().isActive() == true) {
146 em.getTransaction().rollback();
149 // Don't call this unless "em" is not null -hence the check above.
150 JpaStorageUtils.releaseEntityManagerFactory(emf);
157 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.util.List, org.collectionspace.services.common.document.DocumentHandler)
160 public void get(ServiceContext ctx, List<String> csidList, DocumentHandler handler)
161 throws DocumentNotFoundException, DocumentException {
162 throw new UnsupportedOperationException();
166 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
169 public void get(ServiceContext ctx, String id, DocumentHandler handler)
170 throws DocumentNotFoundException, DocumentException {
172 throw new IllegalArgumentException(
173 "get: ctx is missing");
175 if (handler == null) {
176 throw new IllegalArgumentException(
177 "get: handler is missing");
179 EntityManagerFactory emf = null;
180 EntityManager em = null;
182 handler.prepare(Action.GET);
184 o = JpaStorageUtils.getEntity(getEntityName(ctx), id,
187 if (em != null && em.getTransaction().isActive()) {
188 em.getTransaction().rollback();
190 String msg = "could not find entity with id=" + id;
191 throw new DocumentNotFoundException(msg);
193 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(o);
194 handler.handle(Action.GET, wrapDoc);
195 handler.complete(Action.GET, wrapDoc);
196 } catch (DocumentException de) {
198 } catch (Exception e) {
199 if (logger.isDebugEnabled()) {
200 logger.debug("Caught exception ", e);
202 throw new DocumentException(e);
205 JpaStorageUtils.releaseEntityManagerFactory(emf);
211 * @see org.collectionspace.services.common.storage.StorageClient#getAll(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
214 public void getAll(ServiceContext ctx, DocumentHandler handler)
215 throws DocumentNotFoundException, DocumentException {
216 throw new UnsupportedOperationException("use getFiltered instead");
220 * @see org.collectionspace.services.common.storage.StorageClient#getFiltered(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
223 public void getFiltered(ServiceContext ctx, DocumentHandler handler)
224 throws DocumentNotFoundException, DocumentException {
225 QueryContext queryContext = new QueryContext(ctx, handler);
227 DocumentFilter docFilter = handler.getDocumentFilter();
228 if (docFilter == null) {
229 docFilter = handler.createDocumentFilter();
231 EntityManagerFactory emf = null;
232 EntityManager em = null;
234 handler.prepare(Action.GET_ALL);
235 StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
236 queryStrBldr.append(getEntityName(ctx));
237 queryStrBldr.append(" a");
239 List<DocumentFilter.ParamBinding> params = docFilter.buildWhereForSearch(queryStrBldr);
240 emf = JpaStorageUtils.getEntityManagerFactory();
241 em = emf.createEntityManager();
242 String queryStr = queryStrBldr.toString(); //for debugging
243 Query q = em.createQuery(queryStr);
245 for (DocumentFilter.ParamBinding p : params) {
246 q.setParameter(p.getName(), p.getValue());
248 if (docFilter.getOffset() > 0) {
249 q.setFirstResult(docFilter.getOffset());
251 if (docFilter.getPageSize() > 0) {
252 q.setMaxResults(docFilter.getPageSize());
255 //FIXME is transaction required for get?
256 em.getTransaction().begin();
257 List list = q.getResultList();
258 em.getTransaction().commit();
259 DocumentWrapper<List> wrapDoc = new DocumentWrapperImpl<List>(list);
260 handler.handle(Action.GET_ALL, wrapDoc);
261 handler.complete(Action.GET_ALL, wrapDoc);
262 } catch (DocumentException de) {
264 } catch (Exception e) {
265 if (logger.isDebugEnabled()) {
266 logger.debug("Caught exception ", e);
268 throw new DocumentException(e);
271 JpaStorageUtils.releaseEntityManagerFactory(emf);
277 * @see org.collectionspace.services.common.storage.StorageClient#update(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
280 public void update(ServiceContext ctx, String id, DocumentHandler handler)
281 throws BadRequestException, DocumentNotFoundException,
284 throw new IllegalArgumentException(
285 "update: ctx is missing");
287 if (handler == null) {
288 throw new IllegalArgumentException(
289 "update: handler is missing");
291 EntityManagerFactory emf = null;
292 EntityManager em = null;
294 handler.prepare(Action.UPDATE);
295 Object entityReceived = handler.getCommonPart();
296 emf = JpaStorageUtils.getEntityManagerFactory();
297 em = emf.createEntityManager();
298 em.getTransaction().begin();
299 Object entityFound = getEntity(em, id, entityReceived.getClass());
300 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entityFound);
301 handler.handle(Action.UPDATE, wrapDoc);
302 JaxbUtils.setValue(entityFound, "setUpdatedAtItem", Date.class, new Date());
303 em.getTransaction().commit();
304 handler.complete(Action.UPDATE, wrapDoc);
305 } catch (BadRequestException bre) {
306 if (em != null && em.getTransaction().isActive()) {
307 em.getTransaction().rollback();
310 } catch (DocumentException de) {
311 if (em != null && em.getTransaction().isActive()) {
312 em.getTransaction().rollback();
315 } catch (Exception e) {
316 if (logger.isDebugEnabled()) {
317 logger.debug("Caught exception ", e);
319 throw new DocumentException(e);
322 JpaStorageUtils.releaseEntityManagerFactory(emf);
328 * delete removes entity and its child entities
329 * cost: a get before delete
330 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
333 public void delete(ServiceContext ctx, String id)
334 throws DocumentNotFoundException,
337 if (logger.isDebugEnabled()) {
338 logger.debug("delete(ctx, id): deleting entity with id=" + id);
342 throw new IllegalArgumentException(
343 "delete(ctx, id): ctx is missing");
345 EntityManagerFactory emf = null;
346 EntityManager em = null;
349 emf = JpaStorageUtils.getEntityManagerFactory();
350 em = emf.createEntityManager();
352 em.getTransaction().begin();
353 Object entityFound = getEntity(ctx, em, id);
354 if (entityFound == null) {
355 if (em != null && em.getTransaction().isActive()) {
356 em.getTransaction().rollback();
358 String msg = "delete(ctx, id): could not find entity with id=" + id;
360 throw new DocumentNotFoundException(msg);
362 em.remove(entityFound);
363 em.getTransaction().commit();
365 } catch (DocumentException de) {
366 if (em != null && em.getTransaction().isActive()) {
367 em.getTransaction().rollback();
370 } catch (Exception e) {
371 if (logger.isDebugEnabled()) {
372 logger.debug("delete(ctx, id): Caught exception ", e);
374 if (em != null && em.getTransaction().isActive()) {
375 em.getTransaction().rollback();
377 throw new DocumentException(e);
380 JpaStorageUtils.releaseEntityManagerFactory(emf);
386 * deleteWhere uses the where clause to delete an entityReceived represented by the csidReceived
387 * it does not delete any child entities.
390 * @throws DocumentNotFoundException
391 * @throws DocumentException
393 public void deleteWhere(ServiceContext ctx, String id)
394 throws DocumentNotFoundException,
398 throw new IllegalArgumentException(
399 "deleteWhere(ctx, id) : ctx is missing");
402 if (logger.isDebugEnabled()) {
403 logger.debug("deleteWhere(ctx, id): deleting entity with id=" + id);
405 EntityManagerFactory emf = null;
406 EntityManager em = null;
408 StringBuilder deleteStr = new StringBuilder("DELETE FROM ");
409 deleteStr.append(getEntityName(ctx));
410 deleteStr.append(" WHERE csid = :csid and tenantId = :tenantId");
411 //TODO: add tenant csidReceived
413 emf = JpaStorageUtils.getEntityManagerFactory();
414 em = emf.createEntityManager();
415 Query q = em.createQuery(deleteStr.toString());
416 q.setParameter("csid", id);
417 q.setParameter("tenantId", ctx.getTenantId());
420 em.getTransaction().begin();
421 rcount = q.executeUpdate();
423 if (em != null && em.getTransaction().isActive()) {
424 em.getTransaction().rollback();
426 String msg = "deleteWhere(ctx, id) could not find entity with id=" + id;
428 throw new DocumentNotFoundException(msg);
430 em.getTransaction().commit();
432 } catch (DocumentException de) {
433 if (em != null && em.getTransaction().isActive()) {
434 em.getTransaction().rollback();
437 } catch (Exception e) {
438 if (logger.isDebugEnabled()) {
439 logger.debug("deleteWhere(ctx, id) Caught exception ", e);
441 if (em != null && em.getTransaction().isActive()) {
442 em.getTransaction().rollback();
444 throw new DocumentException(e);
447 JpaStorageUtils.releaseEntityManagerFactory(emf);
453 * delete removes entity and its child entities but calls back to given handler
454 * cost: a get before delete
455 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
458 public void delete(ServiceContext ctx, String id, DocumentHandler handler)
459 throws DocumentNotFoundException, DocumentException {
461 throw new IllegalArgumentException(
462 "delete(ctx, ix, handler): ctx is missing");
464 if (handler == null) {
465 throw new IllegalArgumentException(
466 "delete(ctx, ix, handler): handler is missing");
468 EntityManagerFactory emf = null;
469 EntityManager em = null;
471 handler.prepare(Action.DELETE);
473 emf = JpaStorageUtils.getEntityManagerFactory();
474 em = emf.createEntityManager();
476 em.getTransaction().begin();
477 Object entityFound = getEntity(ctx, em, id);
478 if (entityFound == null) {
479 if (em != null && em.getTransaction().isActive()) {
480 em.getTransaction().rollback();
482 String msg = "delete(ctx, ix, handler) could not find entity with id=" + id;
484 throw new DocumentNotFoundException(msg);
486 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entityFound);
487 handler.handle(Action.DELETE, wrapDoc);
488 em.remove(entityFound);
489 em.getTransaction().commit();
491 handler.complete(Action.DELETE, wrapDoc);
492 } catch (DocumentException de) {
493 if (em != null && em.getTransaction().isActive()) {
494 em.getTransaction().rollback();
497 } catch (Exception e) {
498 if (logger.isDebugEnabled()) {
499 logger.debug("delete(ctx, ix, handler): Caught exception ", e);
501 if (em != null && em.getTransaction().isActive()) {
502 em.getTransaction().rollback();
504 throw new DocumentException(e);
507 JpaStorageUtils.releaseEntityManagerFactory(emf);
513 * Gets the entityReceived name.
517 * @return the entityReceived name
519 protected String getEntityName(ServiceContext ctx) {
520 Object o = ctx.getProperty(ServiceContextProperties.ENTITY_NAME);
522 throw new IllegalArgumentException(ServiceContextProperties.ENTITY_NAME
523 + "property is missing in context "
531 * getEntity returns persistent entity for given id. it assumes that
532 * service context has property ServiceContextProperties.ENTITY_CLASS set
533 * rolls back the transaction if not found
534 * @param ctx service context
535 * @param em entity manager
536 * @param csid received
538 * @throws DocumentNotFoundException and rollsback the transaction if active
540 protected Object getEntity(ServiceContext ctx, EntityManager em, String id)
541 throws DocumentNotFoundException {
542 Class entityClazz = (Class) ctx.getProperty(ServiceContextProperties.ENTITY_CLASS);
543 if (entityClazz == null) {
544 String msg = ServiceContextProperties.ENTITY_CLASS
545 + " property is missing in the context";
547 throw new IllegalArgumentException(msg);
549 return getEntity(em, id, entityClazz);
553 * getEntity retrieves the persistent entity of given class for given id
554 * rolls back the transaction if not found
556 * @param id entity id
559 * @throws DocumentNotFoundException and rollsback the transaction if active
561 protected Object getEntity(EntityManager em, String id, Class entityClazz)
562 throws DocumentNotFoundException {
563 Object entityFound = JpaStorageUtils.getEntity(em, id, entityClazz);
564 if (entityFound == null) {
565 if (em != null && em.getTransaction().isActive()) {
566 em.getTransaction().rollback();
568 String msg = "could not find entity of type=" + entityClazz.getName()
571 throw new DocumentNotFoundException(msg);
577 public void get(ServiceContext ctx, DocumentHandler handler)
578 throws DocumentNotFoundException, DocumentException {
579 throw new UnsupportedOperationException();
583 public void doWorkflowTransition(ServiceContext ctx, String id,
584 DocumentHandler handler, TransitionDef transitionDef)
585 throws BadRequestException, DocumentNotFoundException,
587 // Do nothing. JPA services do not support workflow.