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;
23 import javax.persistence.RollbackException;
25 import java.sql.BatchUpdateException;
27 import javax.persistence.EntityManager;
28 import javax.persistence.EntityManagerFactory;
29 import javax.persistence.Query;
31 import org.collectionspace.services.common.document.BadRequestException;
32 import org.collectionspace.services.common.document.DocumentException;
33 import org.collectionspace.services.common.document.DocumentFilter;
34 import org.collectionspace.services.common.document.DocumentHandler;
35 import org.collectionspace.services.common.document.DocumentNotFoundException;
36 import org.collectionspace.services.common.document.DocumentHandler.Action;
37 import org.collectionspace.services.common.document.DocumentWrapper;
38 import org.collectionspace.services.common.document.DocumentWrapperImpl;
39 import org.collectionspace.services.common.document.JaxbUtils;
40 import org.collectionspace.services.common.document.TransactionException;
41 import org.collectionspace.services.common.storage.StorageClient;
42 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.Specifier;
43 import org.collectionspace.services.common.context.ServiceContextProperties;
44 import org.collectionspace.services.common.context.ServiceContext;
45 import org.collectionspace.services.common.query.QueryContext;
46 import org.collectionspace.services.lifecycle.TransitionDef;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
51 * JpaStorageClient is used to perform CRUD operations on SQL storage using JPA.
52 * It uses @see DocumentHandler as IOHandler with the client.
53 * All the operations in this client are carried out under their own transactions.
54 * A call to any method would start and commit/rollback a transaction.
56 * Assumption: each persistent entityReceived has the following 3 attributes
57 <xs:element name="createdAt" type="xs:dateTime">
61 <orm:column name="created_at" nullable="false"/>
66 <xs:element name="updatedAt" type="xs:dateTime">
70 <orm:column name="updated_at" />
76 <xs:attribute name="csid" type="xs:string">
80 <orm:column name="csid" length="128" nullable="false"/>
86 * $LastChangedRevision: $ $LastChangedDate: $
88 public class JpaStorageClientImpl implements StorageClient {
91 private final Logger logger = LoggerFactory.getLogger(JpaStorageClientImpl.class);
94 * Instantiates a new jpa storage client.
96 public JpaStorageClientImpl() {
101 * @see org.collectionspace.services.common.storage.StorageClient#create(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
104 public String create(ServiceContext ctx,
105 DocumentHandler handler) throws BadRequestException,
107 boolean rollbackTransaction = false;
109 throw new IllegalArgumentException(
110 "create: ctx is missing");
112 if (handler == null) {
113 throw new IllegalArgumentException(
114 "create: handler is missing");
116 EntityManagerFactory emf = null;
117 EntityManager em = null;
119 handler.prepare(Action.CREATE);
120 Object entity = handler.getCommonPart();
121 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entity);
122 handler.handle(Action.CREATE, wrapDoc);
123 JaxbUtils.setValue(entity, "setCreatedAtItem", Date.class, new Date());
124 emf = JpaStorageUtils.getEntityManagerFactory();
125 em = emf.createEntityManager();
126 em.getTransaction().begin(); { //begin of transaction block
129 em.getTransaction().commit();
130 handler.complete(Action.CREATE, wrapDoc);
131 return (String) JaxbUtils.getValue(entity, "getCsid");
132 } catch (BadRequestException bre) {
133 rollbackTransaction = true;
135 } catch (DocumentException de) {
136 rollbackTransaction = true;
138 } catch (Exception e) {
139 rollbackTransaction = true;
140 if (logger.isDebugEnabled()) {
141 logger.debug("Caught exception ", e);
143 throw DocumentException.createDocumentException(e);
146 if (rollbackTransaction == true) {
147 if (em.getTransaction().isActive() == true) {
148 em.getTransaction().rollback();
151 // Don't call this unless "em" is not null -hence the check above.
152 JpaStorageUtils.releaseEntityManagerFactory(emf);
159 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.util.List, org.collectionspace.services.common.document.DocumentHandler)
162 public void get(ServiceContext ctx, List<String> csidList, DocumentHandler handler)
163 throws DocumentNotFoundException, DocumentException {
164 throw new UnsupportedOperationException();
168 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
171 public void get(ServiceContext ctx, String id, DocumentHandler handler)
172 throws DocumentNotFoundException, DocumentException {
174 throw new IllegalArgumentException(
175 "get: ctx is missing");
177 if (handler == null) {
178 throw new IllegalArgumentException(
179 "get: handler is missing");
181 EntityManagerFactory emf = null;
182 EntityManager em = null;
184 handler.prepare(Action.GET);
186 o = JpaStorageUtils.getEntity(getEntityName(ctx), id,
189 if (em != null && em.getTransaction().isActive()) {
190 em.getTransaction().rollback();
192 String msg = "could not find entity with id=" + id;
193 throw new DocumentNotFoundException(msg);
195 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(o);
196 handler.handle(Action.GET, wrapDoc);
197 handler.complete(Action.GET, wrapDoc);
198 } catch (DocumentException de) {
200 } catch (Exception e) {
201 if (logger.isDebugEnabled()) {
202 logger.debug("Caught exception ", e);
204 throw new DocumentException(e);
207 JpaStorageUtils.releaseEntityManagerFactory(emf);
213 * @see org.collectionspace.services.common.storage.StorageClient#getAll(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
216 public void getAll(ServiceContext ctx, DocumentHandler handler)
217 throws DocumentNotFoundException, DocumentException {
218 throw new UnsupportedOperationException("use getFiltered instead");
222 * @see org.collectionspace.services.common.storage.StorageClient#getFiltered(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
225 public void getFiltered(ServiceContext ctx, DocumentHandler handler)
226 throws DocumentNotFoundException, DocumentException {
227 QueryContext queryContext = new QueryContext(ctx, handler);
229 DocumentFilter docFilter = handler.getDocumentFilter();
230 if (docFilter == null) {
231 docFilter = handler.createDocumentFilter();
233 EntityManagerFactory emf = null;
234 EntityManager em = null;
236 handler.prepare(Action.GET_ALL);
237 StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
238 queryStrBldr.append(getEntityName(ctx));
239 queryStrBldr.append(" a");
241 List<DocumentFilter.ParamBinding> params = docFilter.buildWhereForSearch(queryStrBldr);
242 emf = JpaStorageUtils.getEntityManagerFactory();
243 em = emf.createEntityManager();
244 String queryStr = queryStrBldr.toString(); //for debugging
245 Query q = em.createQuery(queryStr);
247 for (DocumentFilter.ParamBinding p : params) {
248 q.setParameter(p.getName(), p.getValue());
250 if (docFilter.getOffset() > 0) {
251 q.setFirstResult(docFilter.getOffset());
253 if (docFilter.getPageSize() > 0) {
254 q.setMaxResults(docFilter.getPageSize());
257 //FIXME is transaction required for get?
258 em.getTransaction().begin();
259 List list = q.getResultList();
260 em.getTransaction().commit();
261 DocumentWrapper<List> wrapDoc = new DocumentWrapperImpl<List>(list);
262 handler.handle(Action.GET_ALL, wrapDoc);
263 handler.complete(Action.GET_ALL, wrapDoc);
264 } catch (DocumentException de) {
266 } catch (Exception e) {
267 if (logger.isDebugEnabled()) {
268 logger.debug("Caught exception ", e);
270 throw new DocumentException(e);
273 JpaStorageUtils.releaseEntityManagerFactory(emf);
279 * @see org.collectionspace.services.common.storage.StorageClient#update(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
282 public void update(ServiceContext ctx, String id, DocumentHandler handler)
283 throws BadRequestException, DocumentNotFoundException,
286 throw new IllegalArgumentException(
287 "update: ctx is missing");
289 if (handler == null) {
290 throw new IllegalArgumentException(
291 "update: handler is missing");
293 EntityManagerFactory emf = null;
294 EntityManager em = null;
296 handler.prepare(Action.UPDATE);
297 Object entityReceived = handler.getCommonPart();
298 emf = JpaStorageUtils.getEntityManagerFactory();
299 em = emf.createEntityManager();
300 em.getTransaction().begin();
301 Object entityFound = getEntity(em, id, entityReceived.getClass());
302 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entityFound);
303 handler.handle(Action.UPDATE, wrapDoc);
304 JaxbUtils.setValue(entityFound, "setUpdatedAtItem", Date.class, new Date());
305 em.getTransaction().commit();
306 handler.complete(Action.UPDATE, wrapDoc);
307 } catch (BadRequestException bre) {
308 if (em != null && em.getTransaction().isActive()) {
309 em.getTransaction().rollback();
312 } catch (DocumentException de) {
313 if (em != null && em.getTransaction().isActive()) {
314 em.getTransaction().rollback();
317 } catch (Exception e) {
318 if (logger.isDebugEnabled()) {
319 logger.debug("Caught exception ", e);
321 throw new DocumentException(e);
324 JpaStorageUtils.releaseEntityManagerFactory(emf);
330 * delete removes entity and its child entities
331 * cost: a get before delete
332 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
335 public void delete(ServiceContext ctx, String id)
336 throws DocumentNotFoundException,
339 if (logger.isDebugEnabled()) {
340 logger.debug("delete(ctx, id): deleting entity with id=" + id);
344 throw new IllegalArgumentException(
345 "delete(ctx, id): ctx is missing");
347 EntityManagerFactory emf = null;
348 EntityManager em = null;
351 emf = JpaStorageUtils.getEntityManagerFactory();
352 em = emf.createEntityManager();
354 em.getTransaction().begin();
355 Object entityFound = getEntity(ctx, em, id);
356 if (entityFound == null) {
357 if (em != null && em.getTransaction().isActive()) {
358 em.getTransaction().rollback();
360 String msg = "delete(ctx, id): could not find entity with id=" + id;
362 throw new DocumentNotFoundException(msg);
364 em.remove(entityFound);
365 em.getTransaction().commit();
367 } catch (DocumentException de) {
368 if (em != null && em.getTransaction().isActive()) {
369 em.getTransaction().rollback();
372 } catch (Exception e) {
373 if (logger.isDebugEnabled()) {
374 logger.debug("delete(ctx, id): Caught exception ", e);
376 if (em != null && em.getTransaction().isActive()) {
377 em.getTransaction().rollback();
379 throw new DocumentException(e);
382 JpaStorageUtils.releaseEntityManagerFactory(emf);
388 * deleteWhere uses the where clause to delete an entityReceived represented by the csidReceived
389 * it does not delete any child entities.
392 * @throws DocumentNotFoundException
393 * @throws DocumentException
395 public void deleteWhere(ServiceContext ctx, String id)
396 throws DocumentNotFoundException,
400 throw new IllegalArgumentException(
401 "deleteWhere(ctx, id) : ctx is missing");
404 if (logger.isDebugEnabled()) {
405 logger.debug("deleteWhere(ctx, id): deleting entity with id=" + id);
407 EntityManagerFactory emf = null;
408 EntityManager em = null;
410 StringBuilder deleteStr = new StringBuilder("DELETE FROM ");
411 deleteStr.append(getEntityName(ctx));
412 deleteStr.append(" WHERE csid = :csid and tenantId = :tenantId");
413 //TODO: add tenant csidReceived
415 emf = JpaStorageUtils.getEntityManagerFactory();
416 em = emf.createEntityManager();
417 Query q = em.createQuery(deleteStr.toString());
418 q.setParameter("csid", id);
419 q.setParameter("tenantId", ctx.getTenantId());
422 em.getTransaction().begin();
423 rcount = q.executeUpdate();
425 if (em != null && em.getTransaction().isActive()) {
426 em.getTransaction().rollback();
428 String msg = "deleteWhere(ctx, id) could not find entity with id=" + id;
430 throw new DocumentNotFoundException(msg);
432 em.getTransaction().commit();
434 } catch (DocumentException de) {
435 if (em != null && em.getTransaction().isActive()) {
436 em.getTransaction().rollback();
439 } catch (Exception e) {
440 if (logger.isDebugEnabled()) {
441 logger.debug("deleteWhere(ctx, id) Caught exception ", e);
443 if (em != null && em.getTransaction().isActive()) {
444 em.getTransaction().rollback();
446 throw new DocumentException(e);
449 JpaStorageUtils.releaseEntityManagerFactory(emf);
455 * delete removes entity and its child entities but calls back to given handler
456 * cost: a get before delete
457 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
460 public void delete(ServiceContext ctx, String id, DocumentHandler handler)
461 throws DocumentNotFoundException, DocumentException {
463 throw new IllegalArgumentException(
464 "delete(ctx, ix, handler): ctx is missing");
466 if (handler == null) {
467 throw new IllegalArgumentException(
468 "delete(ctx, ix, handler): handler is missing");
470 EntityManagerFactory emf = null;
471 EntityManager em = null;
473 handler.prepare(Action.DELETE);
475 emf = JpaStorageUtils.getEntityManagerFactory();
476 em = emf.createEntityManager();
478 em.getTransaction().begin();
479 Object entityFound = getEntity(ctx, em, id);
480 if (entityFound == null) {
481 if (em != null && em.getTransaction().isActive()) {
482 em.getTransaction().rollback();
484 String msg = "delete(ctx, ix, handler) could not find entity with id=" + id;
486 throw new DocumentNotFoundException(msg);
488 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entityFound);
489 handler.handle(Action.DELETE, wrapDoc);
490 em.remove(entityFound);
491 em.getTransaction().commit();
493 handler.complete(Action.DELETE, wrapDoc);
494 } catch (DocumentException de) {
495 if (em != null && em.getTransaction().isActive()) {
496 em.getTransaction().rollback();
499 } catch (Exception e) {
500 if (logger.isDebugEnabled()) {
501 logger.debug("delete(ctx, ix, handler): Caught exception ", e);
503 if (em != null && em.getTransaction().isActive()) {
504 em.getTransaction().rollback();
506 throw new DocumentException(e);
509 JpaStorageUtils.releaseEntityManagerFactory(emf);
515 * Gets the entityReceived name.
519 * @return the entityReceived name
521 protected String getEntityName(ServiceContext ctx) {
522 Object o = ctx.getProperty(ServiceContextProperties.ENTITY_NAME);
524 throw new IllegalArgumentException(ServiceContextProperties.ENTITY_NAME
525 + "property is missing in context "
533 * getEntity returns persistent entity for given id. it assumes that
534 * service context has property ServiceContextProperties.ENTITY_CLASS set
535 * rolls back the transaction if not found
536 * @param ctx service context
537 * @param em entity manager
538 * @param csid received
540 * @throws DocumentNotFoundException and rollsback the transaction if active
542 protected Object getEntity(ServiceContext ctx, EntityManager em, String id)
543 throws DocumentNotFoundException {
544 Class entityClazz = (Class) ctx.getProperty(ServiceContextProperties.ENTITY_CLASS);
545 if (entityClazz == null) {
546 String msg = ServiceContextProperties.ENTITY_CLASS
547 + " property is missing in the context";
549 throw new IllegalArgumentException(msg);
551 return getEntity(em, id, entityClazz);
555 * getEntity retrieves the persistent entity of given class for given id
556 * rolls back the transaction if not found
558 * @param id entity id
561 * @throws DocumentNotFoundException and rollsback the transaction if active
563 protected Object getEntity(EntityManager em, String id, Class entityClazz)
564 throws DocumentNotFoundException {
565 Object entityFound = JpaStorageUtils.getEntity(em, id, entityClazz);
566 if (entityFound == null) {
567 if (em != null && em.getTransaction().isActive()) {
568 em.getTransaction().rollback();
570 String msg = "could not find entity of type=" + entityClazz.getName()
573 throw new DocumentNotFoundException(msg);
579 public void get(ServiceContext ctx, DocumentHandler handler)
580 throws DocumentNotFoundException, DocumentException {
581 throw new UnsupportedOperationException();
585 public void doWorkflowTransition(ServiceContext ctx, String id,
586 DocumentHandler handler, TransitionDef transitionDef)
587 throws BadRequestException, DocumentNotFoundException,
589 // Do nothing. JPA services do not support workflow.
593 public void deleteWithWhereClause(ServiceContext ctx, String whereClause,
594 DocumentHandler handler) throws DocumentNotFoundException,
596 throw new UnsupportedOperationException();
600 public void synchronize(ServiceContext ctx, Specifier specifier,
601 DocumentHandler handler) throws DocumentNotFoundException,
602 TransactionException, DocumentException {
603 // TODO Auto-generated method stub
604 // Do nothing. Subclasses can override if they want/need to.