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;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
48 * JpaStorageClient is used to perform CRUD operations on SQL storage using JPA.
49 * It uses @see DocumentHandler as IOHandler with the client.
50 * All the operations in this client are carried out under their own transactions.
51 * A call to any method would start and commit/rollback a transaction.
53 * Assumption: each persistent entityReceived has the following 3 attributes
54 <xs:element name="createdAt" type="xs:dateTime">
58 <orm:column name="created_at" nullable="false"/>
63 <xs:element name="updatedAt" type="xs:dateTime">
67 <orm:column name="updated_at" />
73 <xs:attribute name="csid" type="xs:string">
77 <orm:column name="csid" length="128" nullable="false"/>
83 * $LastChangedRevision: $ $LastChangedDate: $
85 public class JpaStorageClientImpl implements StorageClient {
88 private final Logger logger = LoggerFactory.getLogger(JpaStorageClientImpl.class);
91 * Instantiates a new jpa storage client.
93 public JpaStorageClientImpl() {
98 * @see org.collectionspace.services.common.storage.StorageClient#create(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
101 public String create(ServiceContext ctx,
102 DocumentHandler handler) throws BadRequestException,
104 boolean rollbackTransaction = false;
106 throw new IllegalArgumentException(
107 "create: ctx is missing");
109 if (handler == null) {
110 throw new IllegalArgumentException(
111 "create: handler is missing");
113 EntityManagerFactory emf = null;
114 EntityManager em = null;
116 handler.prepare(Action.CREATE);
117 Object entity = handler.getCommonPart();
118 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entity);
119 handler.handle(Action.CREATE, wrapDoc);
120 JaxbUtils.setValue(entity, "setCreatedAtItem", Date.class, new Date());
121 emf = JpaStorageUtils.getEntityManagerFactory();
122 em = emf.createEntityManager();
123 em.getTransaction().begin(); { //begin of transaction block
126 em.getTransaction().commit();
127 handler.complete(Action.CREATE, wrapDoc);
128 return (String) JaxbUtils.getValue(entity, "getCsid");
129 } catch (BadRequestException bre) {
130 rollbackTransaction = true;
132 } catch (DocumentException de) {
133 rollbackTransaction = true;
135 } catch (Exception e) {
136 rollbackTransaction = true;
137 if (logger.isDebugEnabled()) {
138 logger.debug("Caught exception ", e);
140 throw DocumentException.createDocumentException(e);
143 if (rollbackTransaction == true) {
144 if (em.getTransaction().isActive() == true) {
145 em.getTransaction().rollback();
148 // Don't call this unless "em" is not null -hence the check above.
149 JpaStorageUtils.releaseEntityManagerFactory(emf);
156 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.util.List, org.collectionspace.services.common.document.DocumentHandler)
159 public void get(ServiceContext ctx, List<String> csidList, DocumentHandler handler)
160 throws DocumentNotFoundException, DocumentException {
161 throw new UnsupportedOperationException();
165 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
168 public void get(ServiceContext ctx, String id, DocumentHandler handler)
169 throws DocumentNotFoundException, DocumentException {
171 throw new IllegalArgumentException(
172 "get: ctx is missing");
174 if (handler == null) {
175 throw new IllegalArgumentException(
176 "get: handler is missing");
178 EntityManagerFactory emf = null;
179 EntityManager em = null;
181 handler.prepare(Action.GET);
183 o = JpaStorageUtils.getEntity(getEntityName(ctx), id,
186 if (em != null && em.getTransaction().isActive()) {
187 em.getTransaction().rollback();
189 String msg = "could not find entity with id=" + id;
190 throw new DocumentNotFoundException(msg);
192 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(o);
193 handler.handle(Action.GET, wrapDoc);
194 handler.complete(Action.GET, wrapDoc);
195 } catch (DocumentException de) {
197 } catch (Exception e) {
198 if (logger.isDebugEnabled()) {
199 logger.debug("Caught exception ", e);
201 throw new DocumentException(e);
204 JpaStorageUtils.releaseEntityManagerFactory(emf);
210 * @see org.collectionspace.services.common.storage.StorageClient#getAll(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
213 public void getAll(ServiceContext ctx, DocumentHandler handler)
214 throws DocumentNotFoundException, DocumentException {
215 throw new UnsupportedOperationException("use getFiltered instead");
219 * @see org.collectionspace.services.common.storage.StorageClient#getFiltered(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
222 public void getFiltered(ServiceContext ctx, DocumentHandler handler)
223 throws DocumentNotFoundException, DocumentException {
224 QueryContext queryContext = new QueryContext(ctx, handler);
226 DocumentFilter docFilter = handler.getDocumentFilter();
227 if (docFilter == null) {
228 docFilter = handler.createDocumentFilter();
230 EntityManagerFactory emf = null;
231 EntityManager em = null;
233 handler.prepare(Action.GET_ALL);
234 StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
235 queryStrBldr.append(getEntityName(ctx));
236 queryStrBldr.append(" a");
238 List<DocumentFilter.ParamBinding> params = docFilter.buildWhereForSearch(queryStrBldr);
239 emf = JpaStorageUtils.getEntityManagerFactory();
240 em = emf.createEntityManager();
241 String queryStr = queryStrBldr.toString(); //for debugging
242 Query q = em.createQuery(queryStr);
244 for (DocumentFilter.ParamBinding p : params) {
245 q.setParameter(p.getName(), p.getValue());
247 if (docFilter.getOffset() > 0) {
248 q.setFirstResult(docFilter.getOffset());
250 if (docFilter.getPageSize() > 0) {
251 q.setMaxResults(docFilter.getPageSize());
254 //FIXME is transaction required for get?
255 em.getTransaction().begin();
256 List list = q.getResultList();
257 em.getTransaction().commit();
258 DocumentWrapper<List> wrapDoc = new DocumentWrapperImpl<List>(list);
259 handler.handle(Action.GET_ALL, wrapDoc);
260 handler.complete(Action.GET_ALL, wrapDoc);
261 } catch (DocumentException de) {
263 } catch (Exception e) {
264 if (logger.isDebugEnabled()) {
265 logger.debug("Caught exception ", e);
267 throw new DocumentException(e);
270 JpaStorageUtils.releaseEntityManagerFactory(emf);
276 * @see org.collectionspace.services.common.storage.StorageClient#update(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
279 public void update(ServiceContext ctx, String id, DocumentHandler handler)
280 throws BadRequestException, DocumentNotFoundException,
283 throw new IllegalArgumentException(
284 "update: ctx is missing");
286 if (handler == null) {
287 throw new IllegalArgumentException(
288 "update: handler is missing");
290 EntityManagerFactory emf = null;
291 EntityManager em = null;
293 handler.prepare(Action.UPDATE);
294 Object entityReceived = handler.getCommonPart();
295 emf = JpaStorageUtils.getEntityManagerFactory();
296 em = emf.createEntityManager();
297 em.getTransaction().begin();
298 Object entityFound = getEntity(em, id, entityReceived.getClass());
299 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entityFound);
300 handler.handle(Action.UPDATE, wrapDoc);
301 JaxbUtils.setValue(entityFound, "setUpdatedAtItem", Date.class, new Date());
302 em.getTransaction().commit();
303 handler.complete(Action.UPDATE, wrapDoc);
304 } catch (BadRequestException bre) {
305 if (em != null && em.getTransaction().isActive()) {
306 em.getTransaction().rollback();
309 } catch (DocumentException de) {
310 if (em != null && em.getTransaction().isActive()) {
311 em.getTransaction().rollback();
314 } catch (Exception e) {
315 if (logger.isDebugEnabled()) {
316 logger.debug("Caught exception ", e);
318 throw new DocumentException(e);
321 JpaStorageUtils.releaseEntityManagerFactory(emf);
327 * delete removes entity and its child entities
328 * cost: a get before delete
329 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
332 public void delete(ServiceContext ctx, String id)
333 throws DocumentNotFoundException,
336 if (logger.isDebugEnabled()) {
337 logger.debug("delete(ctx, id): deleting entity with id=" + id);
341 throw new IllegalArgumentException(
342 "delete(ctx, id): ctx is missing");
344 EntityManagerFactory emf = null;
345 EntityManager em = null;
348 emf = JpaStorageUtils.getEntityManagerFactory();
349 em = emf.createEntityManager();
351 em.getTransaction().begin();
352 Object entityFound = getEntity(ctx, em, id);
353 if (entityFound == null) {
354 if (em != null && em.getTransaction().isActive()) {
355 em.getTransaction().rollback();
357 String msg = "delete(ctx, id): could not find entity with id=" + id;
359 throw new DocumentNotFoundException(msg);
361 em.remove(entityFound);
362 em.getTransaction().commit();
364 } catch (DocumentException de) {
365 if (em != null && em.getTransaction().isActive()) {
366 em.getTransaction().rollback();
369 } catch (Exception e) {
370 if (logger.isDebugEnabled()) {
371 logger.debug("delete(ctx, id): Caught exception ", e);
373 if (em != null && em.getTransaction().isActive()) {
374 em.getTransaction().rollback();
376 throw new DocumentException(e);
379 JpaStorageUtils.releaseEntityManagerFactory(emf);
385 * deleteWhere uses the where clause to delete an entityReceived represented by the csidReceived
386 * it does not delete any child entities.
389 * @throws DocumentNotFoundException
390 * @throws DocumentException
392 public void deleteWhere(ServiceContext ctx, String id)
393 throws DocumentNotFoundException,
397 throw new IllegalArgumentException(
398 "deleteWhere(ctx, id) : ctx is missing");
401 if (logger.isDebugEnabled()) {
402 logger.debug("deleteWhere(ctx, id): deleting entity with id=" + id);
404 EntityManagerFactory emf = null;
405 EntityManager em = null;
407 StringBuilder deleteStr = new StringBuilder("DELETE FROM ");
408 deleteStr.append(getEntityName(ctx));
409 deleteStr.append(" WHERE csid = :csid and tenantId = :tenantId");
410 //TODO: add tenant csidReceived
412 emf = JpaStorageUtils.getEntityManagerFactory();
413 em = emf.createEntityManager();
414 Query q = em.createQuery(deleteStr.toString());
415 q.setParameter("csid", id);
416 q.setParameter("tenantId", ctx.getTenantId());
419 em.getTransaction().begin();
420 rcount = q.executeUpdate();
422 if (em != null && em.getTransaction().isActive()) {
423 em.getTransaction().rollback();
425 String msg = "deleteWhere(ctx, id) could not find entity with id=" + id;
427 throw new DocumentNotFoundException(msg);
429 em.getTransaction().commit();
431 } catch (DocumentException de) {
432 if (em != null && em.getTransaction().isActive()) {
433 em.getTransaction().rollback();
436 } catch (Exception e) {
437 if (logger.isDebugEnabled()) {
438 logger.debug("deleteWhere(ctx, id) Caught exception ", e);
440 if (em != null && em.getTransaction().isActive()) {
441 em.getTransaction().rollback();
443 throw new DocumentException(e);
446 JpaStorageUtils.releaseEntityManagerFactory(emf);
452 * delete removes entity and its child entities but calls back to given handler
453 * cost: a get before delete
454 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
457 public void delete(ServiceContext ctx, String id, DocumentHandler handler)
458 throws DocumentNotFoundException, DocumentException {
460 throw new IllegalArgumentException(
461 "delete(ctx, ix, handler): ctx is missing");
463 if (handler == null) {
464 throw new IllegalArgumentException(
465 "delete(ctx, ix, handler): handler is missing");
467 EntityManagerFactory emf = null;
468 EntityManager em = null;
470 handler.prepare(Action.DELETE);
472 emf = JpaStorageUtils.getEntityManagerFactory();
473 em = emf.createEntityManager();
475 em.getTransaction().begin();
476 Object entityFound = getEntity(ctx, em, id);
477 if (entityFound == null) {
478 if (em != null && em.getTransaction().isActive()) {
479 em.getTransaction().rollback();
481 String msg = "delete(ctx, ix, handler) could not find entity with id=" + id;
483 throw new DocumentNotFoundException(msg);
485 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entityFound);
486 handler.handle(Action.DELETE, wrapDoc);
487 em.remove(entityFound);
488 em.getTransaction().commit();
490 handler.complete(Action.DELETE, wrapDoc);
491 } catch (DocumentException de) {
492 if (em != null && em.getTransaction().isActive()) {
493 em.getTransaction().rollback();
496 } catch (Exception e) {
497 if (logger.isDebugEnabled()) {
498 logger.debug("delete(ctx, ix, handler): Caught exception ", e);
500 if (em != null && em.getTransaction().isActive()) {
501 em.getTransaction().rollback();
503 throw new DocumentException(e);
506 JpaStorageUtils.releaseEntityManagerFactory(emf);
512 * Gets the entityReceived name.
516 * @return the entityReceived name
518 protected String getEntityName(ServiceContext ctx) {
519 Object o = ctx.getProperty(ServiceContextProperties.ENTITY_NAME);
521 throw new IllegalArgumentException(ServiceContextProperties.ENTITY_NAME
522 + "property is missing in context "
530 * getEntity returns persistent entity for given id. it assumes that
531 * service context has property ServiceContextProperties.ENTITY_CLASS set
532 * rolls back the transaction if not found
533 * @param ctx service context
534 * @param em entity manager
535 * @param csid received
537 * @throws DocumentNotFoundException and rollsback the transaction if active
539 protected Object getEntity(ServiceContext ctx, EntityManager em, String id)
540 throws DocumentNotFoundException {
541 Class entityClazz = (Class) ctx.getProperty(ServiceContextProperties.ENTITY_CLASS);
542 if (entityClazz == null) {
543 String msg = ServiceContextProperties.ENTITY_CLASS
544 + " property is missing in the context";
546 throw new IllegalArgumentException(msg);
548 return getEntity(em, id, entityClazz);
552 * getEntity retrieves the persistent entity of given class for given id
553 * rolls back the transaction if not found
555 * @param id entity id
558 * @throws DocumentNotFoundException and rollsback the transaction if active
560 protected Object getEntity(EntityManager em, String id, Class entityClazz)
561 throws DocumentNotFoundException {
562 Object entityFound = JpaStorageUtils.getEntity(em, id, entityClazz);
563 if (entityFound == null) {
564 if (em != null && em.getTransaction().isActive()) {
565 em.getTransaction().rollback();
567 String msg = "could not find entity of type=" + entityClazz.getName()
570 throw new DocumentNotFoundException(msg);
576 public void get(ServiceContext ctx, DocumentHandler handler)
577 throws DocumentNotFoundException, DocumentException {
578 throw new UnsupportedOperationException();