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.AuthorityItemSpecifier;
43 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.Specifier;
44 import org.collectionspace.services.common.context.ServiceContextProperties;
45 import org.collectionspace.services.common.context.ServiceContext;
46 import org.collectionspace.services.common.query.QueryContext;
47 import org.collectionspace.services.lifecycle.TransitionDef;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
52 * JpaStorageClient is used to perform CRUD operations on SQL storage using JPA.
53 * It uses @see DocumentHandler as IOHandler with the client.
54 * All the operations in this client are carried out under their own transactions.
55 * A call to any method would start and commit/rollback a transaction.
57 * Assumption: each persistent entityReceived has the following 3 attributes
58 <xs:element name="createdAt" type="xs:dateTime">
62 <orm:column name="created_at" nullable="false"/>
67 <xs:element name="updatedAt" type="xs:dateTime">
71 <orm:column name="updated_at" />
77 <xs:attribute name="csid" type="xs:string">
81 <orm:column name="csid" length="128" nullable="false"/>
87 * $LastChangedRevision: $ $LastChangedDate: $
89 public class JpaStorageClientImpl implements StorageClient {
92 private final Logger logger = LoggerFactory.getLogger(JpaStorageClientImpl.class);
95 * Instantiates a new jpa storage client.
97 public JpaStorageClientImpl() {
102 * @see org.collectionspace.services.common.storage.StorageClient#create(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
105 public String create(ServiceContext ctx,
106 DocumentHandler handler) throws BadRequestException,
108 boolean rollbackTransaction = false;
110 throw new IllegalArgumentException(
111 "create: ctx is missing");
113 if (handler == null) {
114 throw new IllegalArgumentException(
115 "create: handler is missing");
117 EntityManagerFactory emf = null;
118 EntityManager em = null;
120 handler.prepare(Action.CREATE);
121 Object entity = handler.getCommonPart();
122 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entity);
123 handler.handle(Action.CREATE, wrapDoc);
124 JaxbUtils.setValue(entity, "setCreatedAtItem", Date.class, new Date());
125 emf = JpaStorageUtils.getEntityManagerFactory();
126 em = emf.createEntityManager();
127 em.getTransaction().begin(); { //begin of transaction block
130 em.getTransaction().commit();
131 handler.complete(Action.CREATE, wrapDoc);
132 return (String) JaxbUtils.getValue(entity, "getCsid");
133 } catch (BadRequestException bre) {
134 rollbackTransaction = true;
136 } catch (DocumentException de) {
137 rollbackTransaction = true;
139 } catch (Exception e) {
140 rollbackTransaction = true;
141 if (logger.isDebugEnabled()) {
142 logger.debug("Caught exception ", e);
144 throw DocumentException.createDocumentException(e);
147 if (rollbackTransaction == true) {
148 if (em.getTransaction().isActive() == true) {
149 em.getTransaction().rollback();
152 // Don't call this unless "em" is not null -hence the check above.
153 JpaStorageUtils.releaseEntityManagerFactory(emf);
160 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.util.List, org.collectionspace.services.common.document.DocumentHandler)
163 public void get(ServiceContext ctx, List<String> csidList, DocumentHandler handler)
164 throws DocumentNotFoundException, DocumentException {
165 throw new UnsupportedOperationException();
169 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
172 public void get(ServiceContext ctx, String id, DocumentHandler handler)
173 throws DocumentNotFoundException, DocumentException {
175 throw new IllegalArgumentException(
176 "get: ctx is missing");
178 if (handler == null) {
179 throw new IllegalArgumentException(
180 "get: handler is missing");
182 EntityManagerFactory emf = null;
183 EntityManager em = null;
185 handler.prepare(Action.GET);
187 o = JpaStorageUtils.getEntity(getEntityName(ctx), id,
190 if (em != null && em.getTransaction().isActive()) {
191 em.getTransaction().rollback();
193 String msg = "could not find entity with id=" + id;
194 throw new DocumentNotFoundException(msg);
196 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(o);
197 handler.handle(Action.GET, wrapDoc);
198 handler.complete(Action.GET, wrapDoc);
199 } catch (DocumentException de) {
201 } catch (Exception e) {
202 if (logger.isDebugEnabled()) {
203 logger.debug("Caught exception ", e);
205 throw new DocumentException(e);
208 JpaStorageUtils.releaseEntityManagerFactory(emf);
214 * @see org.collectionspace.services.common.storage.StorageClient#getAll(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
217 public void getAll(ServiceContext ctx, DocumentHandler handler)
218 throws DocumentNotFoundException, DocumentException {
219 throw new UnsupportedOperationException("use getFiltered instead");
223 * @see org.collectionspace.services.common.storage.StorageClient#getFiltered(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
226 public void getFiltered(ServiceContext ctx, DocumentHandler handler)
227 throws DocumentNotFoundException, DocumentException {
228 QueryContext queryContext = new QueryContext(ctx, handler);
230 DocumentFilter docFilter = handler.getDocumentFilter();
231 if (docFilter == null) {
232 docFilter = handler.createDocumentFilter();
234 EntityManagerFactory emf = null;
235 EntityManager em = null;
237 handler.prepare(Action.GET_ALL);
238 StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
239 queryStrBldr.append(getEntityName(ctx));
240 queryStrBldr.append(" a");
242 List<DocumentFilter.ParamBinding> params = docFilter.buildWhereForSearch(queryStrBldr);
243 emf = JpaStorageUtils.getEntityManagerFactory();
244 em = emf.createEntityManager();
245 String queryStr = queryStrBldr.toString(); //for debugging
246 Query q = em.createQuery(queryStr);
248 for (DocumentFilter.ParamBinding p : params) {
249 q.setParameter(p.getName(), p.getValue());
251 if (docFilter.getOffset() > 0) {
252 q.setFirstResult(docFilter.getOffset());
254 if (docFilter.getPageSize() > 0) {
255 q.setMaxResults(docFilter.getPageSize());
258 //FIXME is transaction required for get?
259 em.getTransaction().begin();
260 List list = q.getResultList();
261 em.getTransaction().commit();
262 DocumentWrapper<List> wrapDoc = new DocumentWrapperImpl<List>(list);
263 handler.handle(Action.GET_ALL, wrapDoc);
264 handler.complete(Action.GET_ALL, wrapDoc);
265 } catch (DocumentException de) {
267 } catch (Exception e) {
268 if (logger.isDebugEnabled()) {
269 logger.debug("Caught exception ", e);
271 throw new DocumentException(e);
274 JpaStorageUtils.releaseEntityManagerFactory(emf);
280 * @see org.collectionspace.services.common.storage.StorageClient#update(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
283 public void update(ServiceContext ctx, String id, DocumentHandler handler)
284 throws BadRequestException, DocumentNotFoundException,
287 throw new IllegalArgumentException(
288 "update: ctx is missing");
290 if (handler == null) {
291 throw new IllegalArgumentException(
292 "update: handler is missing");
294 EntityManagerFactory emf = null;
295 EntityManager em = null;
297 handler.prepare(Action.UPDATE);
298 Object entityReceived = handler.getCommonPart();
299 emf = JpaStorageUtils.getEntityManagerFactory();
300 em = emf.createEntityManager();
301 em.getTransaction().begin();
302 Object entityFound = getEntity(em, id, entityReceived.getClass());
303 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entityFound);
304 handler.handle(Action.UPDATE, wrapDoc);
305 JaxbUtils.setValue(entityFound, "setUpdatedAtItem", Date.class, new Date());
306 em.getTransaction().commit();
307 handler.complete(Action.UPDATE, wrapDoc);
308 } catch (BadRequestException bre) {
309 if (em != null && em.getTransaction().isActive()) {
310 em.getTransaction().rollback();
313 } catch (DocumentException de) {
314 if (em != null && em.getTransaction().isActive()) {
315 em.getTransaction().rollback();
318 } catch (Exception e) {
319 if (logger.isDebugEnabled()) {
320 logger.debug("Caught exception ", e);
322 throw new DocumentException(e);
325 JpaStorageUtils.releaseEntityManagerFactory(emf);
331 * delete removes entity and its child entities
332 * cost: a get before delete
333 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
336 public void delete(ServiceContext ctx, String id)
337 throws DocumentNotFoundException,
340 if (logger.isDebugEnabled()) {
341 logger.debug("delete(ctx, id): deleting entity with id=" + id);
345 throw new IllegalArgumentException(
346 "delete(ctx, id): ctx is missing");
348 EntityManagerFactory emf = null;
349 EntityManager em = null;
352 emf = JpaStorageUtils.getEntityManagerFactory();
353 em = emf.createEntityManager();
355 em.getTransaction().begin();
356 Object entityFound = getEntity(ctx, em, id);
357 if (entityFound == null) {
358 if (em != null && em.getTransaction().isActive()) {
359 em.getTransaction().rollback();
361 String msg = "delete(ctx, id): could not find entity with id=" + id;
363 throw new DocumentNotFoundException(msg);
365 em.remove(entityFound);
366 em.getTransaction().commit();
368 } catch (DocumentException de) {
369 if (em != null && em.getTransaction().isActive()) {
370 em.getTransaction().rollback();
373 } catch (Exception e) {
374 if (logger.isDebugEnabled()) {
375 logger.debug("delete(ctx, id): Caught exception ", e);
377 if (em != null && em.getTransaction().isActive()) {
378 em.getTransaction().rollback();
380 throw new DocumentException(e);
383 JpaStorageUtils.releaseEntityManagerFactory(emf);
389 * deleteWhere uses the where clause to delete an entityReceived represented by the csidReceived
390 * it does not delete any child entities.
393 * @throws DocumentNotFoundException
394 * @throws DocumentException
396 public void deleteWhere(ServiceContext ctx, String id)
397 throws DocumentNotFoundException,
401 throw new IllegalArgumentException(
402 "deleteWhere(ctx, id) : ctx is missing");
405 if (logger.isDebugEnabled()) {
406 logger.debug("deleteWhere(ctx, id): deleting entity with id=" + id);
408 EntityManagerFactory emf = null;
409 EntityManager em = null;
411 StringBuilder deleteStr = new StringBuilder("DELETE FROM ");
412 deleteStr.append(getEntityName(ctx));
413 deleteStr.append(" WHERE csid = :csid and tenantId = :tenantId");
414 //TODO: add tenant csidReceived
416 emf = JpaStorageUtils.getEntityManagerFactory();
417 em = emf.createEntityManager();
418 Query q = em.createQuery(deleteStr.toString());
419 q.setParameter("csid", id);
420 q.setParameter("tenantId", ctx.getTenantId());
423 em.getTransaction().begin();
424 rcount = q.executeUpdate();
426 if (em != null && em.getTransaction().isActive()) {
427 em.getTransaction().rollback();
429 String msg = "deleteWhere(ctx, id) could not find entity with id=" + id;
431 throw new DocumentNotFoundException(msg);
433 em.getTransaction().commit();
435 } catch (DocumentException de) {
436 if (em != null && em.getTransaction().isActive()) {
437 em.getTransaction().rollback();
440 } catch (Exception e) {
441 if (logger.isDebugEnabled()) {
442 logger.debug("deleteWhere(ctx, id) Caught exception ", e);
444 if (em != null && em.getTransaction().isActive()) {
445 em.getTransaction().rollback();
447 throw new DocumentException(e);
450 JpaStorageUtils.releaseEntityManagerFactory(emf);
456 * delete removes entity and its child entities but calls back to given handler
457 * cost: a get before delete
458 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
461 public void delete(ServiceContext ctx, String id, DocumentHandler handler)
462 throws DocumentNotFoundException, DocumentException {
464 throw new IllegalArgumentException(
465 "delete(ctx, ix, handler): ctx is missing");
467 if (handler == null) {
468 throw new IllegalArgumentException(
469 "delete(ctx, ix, handler): handler is missing");
471 EntityManagerFactory emf = null;
472 EntityManager em = null;
474 handler.prepare(Action.DELETE);
476 emf = JpaStorageUtils.getEntityManagerFactory();
477 em = emf.createEntityManager();
479 em.getTransaction().begin();
480 Object entityFound = getEntity(ctx, em, id);
481 if (entityFound == null) {
482 if (em != null && em.getTransaction().isActive()) {
483 em.getTransaction().rollback();
485 String msg = "delete(ctx, ix, handler) could not find entity with id=" + id;
487 throw new DocumentNotFoundException(msg);
489 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entityFound);
490 handler.handle(Action.DELETE, wrapDoc);
491 em.remove(entityFound);
492 em.getTransaction().commit();
494 handler.complete(Action.DELETE, wrapDoc);
495 } catch (DocumentException de) {
496 if (em != null && em.getTransaction().isActive()) {
497 em.getTransaction().rollback();
500 } catch (Exception e) {
501 if (logger.isDebugEnabled()) {
502 logger.debug("delete(ctx, ix, handler): Caught exception ", e);
504 if (em != null && em.getTransaction().isActive()) {
505 em.getTransaction().rollback();
507 throw new DocumentException(e);
510 JpaStorageUtils.releaseEntityManagerFactory(emf);
516 * Gets the entityReceived name.
520 * @return the entityReceived name
522 protected String getEntityName(ServiceContext ctx) {
523 Object o = ctx.getProperty(ServiceContextProperties.ENTITY_NAME);
525 throw new IllegalArgumentException(ServiceContextProperties.ENTITY_NAME
526 + "property is missing in context "
534 * getEntity returns persistent entity for given id. it assumes that
535 * service context has property ServiceContextProperties.ENTITY_CLASS set
536 * rolls back the transaction if not found
537 * @param ctx service context
538 * @param em entity manager
539 * @param csid received
541 * @throws DocumentNotFoundException and rollsback the transaction if active
543 protected Object getEntity(ServiceContext ctx, EntityManager em, String id)
544 throws DocumentNotFoundException {
545 Class entityClazz = (Class) ctx.getProperty(ServiceContextProperties.ENTITY_CLASS);
546 if (entityClazz == null) {
547 String msg = ServiceContextProperties.ENTITY_CLASS
548 + " property is missing in the context";
550 throw new IllegalArgumentException(msg);
552 return getEntity(em, id, entityClazz);
556 * getEntity retrieves the persistent entity of given class for given id
557 * rolls back the transaction if not found
559 * @param id entity id
562 * @throws DocumentNotFoundException and rollsback the transaction if active
564 protected Object getEntity(EntityManager em, String id, Class entityClazz)
565 throws DocumentNotFoundException {
566 Object entityFound = JpaStorageUtils.getEntity(em, id, entityClazz);
567 if (entityFound == null) {
568 if (em != null && em.getTransaction().isActive()) {
569 em.getTransaction().rollback();
571 String msg = "could not find entity of type=" + entityClazz.getName()
574 throw new DocumentNotFoundException(msg);
580 public void get(ServiceContext ctx, DocumentHandler handler)
581 throws DocumentNotFoundException, DocumentException {
582 throw new UnsupportedOperationException();
586 public void doWorkflowTransition(ServiceContext ctx, String id,
587 DocumentHandler handler, TransitionDef transitionDef)
588 throws BadRequestException, DocumentNotFoundException,
590 // Do nothing. JPA services do not support workflow.
594 public void deleteWithWhereClause(ServiceContext ctx, String whereClause,
595 DocumentHandler handler) throws DocumentNotFoundException,
597 throw new UnsupportedOperationException();
601 public boolean synchronize(ServiceContext ctx, Object specifier,
602 DocumentHandler handler) throws DocumentNotFoundException,
603 TransactionException, DocumentException {
604 // TODO Auto-generated method stub
605 // Do nothing. Subclasses can override if they want/need to.
610 public boolean synchronizeItem(ServiceContext ctx, AuthorityItemSpecifier itemSpecifier,
611 DocumentHandler handler) throws DocumentNotFoundException,
612 TransactionException, DocumentException {
613 // TODO Auto-generated method stub
614 // Do nothing. Subclasses can override if they want/need to.