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.EntityExistsException;
28 import javax.persistence.EntityManager;
29 import javax.persistence.EntityManagerFactory;
30 import javax.persistence.Query;
32 import org.collectionspace.services.common.document.BadRequestException;
33 import org.collectionspace.services.common.document.DocumentException;
34 import org.collectionspace.services.common.document.DocumentFilter;
35 import org.collectionspace.services.common.document.DocumentHandler;
36 import org.collectionspace.services.common.document.DocumentNotFoundException;
37 import org.collectionspace.services.common.document.DocumentHandler.Action;
38 import org.collectionspace.services.common.document.DocumentWrapper;
39 import org.collectionspace.services.common.document.DocumentWrapperImpl;
40 import org.collectionspace.services.common.document.JaxbUtils;
41 import org.collectionspace.services.common.document.TransactionException;
42 import org.collectionspace.services.common.storage.StorageClient;
43 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthorityItemSpecifier;
44 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.Specifier;
45 import org.collectionspace.services.common.context.ServiceContextProperties;
46 import org.collectionspace.services.common.authorization_mgt.AuthorizationStore;
47 import org.collectionspace.services.common.context.ServiceContext;
48 import org.collectionspace.services.common.query.QueryContext;
49 import org.collectionspace.services.lifecycle.TransitionDef;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
54 * JpaStorageClient is used to perform CRUD operations on SQL storage using JPA.
55 * It uses @see DocumentHandler as IOHandler with the client.
56 * All the operations in this client are carried out under their own transactions.
57 * A call to any method would start and commit/rollback a transaction.
59 * Assumption: each persistent entityReceived has the following 3 attributes
60 <xs:element name="createdAt" type="xs:dateTime">
64 <orm:column name="created_at" nullable="false"/>
69 <xs:element name="updatedAt" type="xs:dateTime">
73 <orm:column name="updated_at" />
79 <xs:attribute name="csid" type="xs:string">
83 <orm:column name="csid" length="128" nullable="false"/>
89 * $LastChangedRevision: $ $LastChangedDate: $
91 public class JpaStorageClientImpl implements StorageClient {
94 private final Logger logger = LoggerFactory.getLogger(JpaStorageClientImpl.class);
97 * Instantiates a new jpa storage client.
99 public JpaStorageClientImpl() {
100 //intentionally empty
104 * @see org.collectionspace.services.common.storage.StorageClient#create(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
107 public String create(ServiceContext ctx,
108 DocumentHandler handler) throws BadRequestException,
111 throw new IllegalArgumentException(
112 "create: ctx is missing");
114 if (handler == null) {
115 throw new IllegalArgumentException(
116 "create: handler is missing");
119 boolean rollbackTransaction = false;
120 EntityManagerFactory emf = null;
121 EntityManager em = null;
123 handler.prepare(Action.CREATE);
124 Object entity = handler.getCommonPart();
125 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entity);
127 emf = JpaStorageUtils.getEntityManagerFactory();
128 em = emf.createEntityManager();
129 em.getTransaction().begin(); { //begin of transaction block
130 ctx.setProperty(AuthorizationStore.ENTITY_MANAGER_PROP_KEY, em);
132 handler.handle(Action.CREATE, wrapDoc);
133 JaxbUtils.setValue(entity, "setCreatedAtItem", Date.class, new Date());
135 } catch (EntityExistsException ee) {
137 // We found an existing matching entity in the store, so we don't need to create one. Just update the transient 'entity' instance with the existing persisted entity we found.
139 entity = wrapDoc.getWrappedObject(); // the handler should have reset the wrapped transient object with the existing persisted entity we just found.
142 em.getTransaction().commit();
143 handler.complete(Action.CREATE, wrapDoc);
144 return (String) JaxbUtils.getValue(entity, "getCsid");
145 } catch (BadRequestException bre) {
146 rollbackTransaction = true;
148 } catch (DocumentException de) {
149 rollbackTransaction = true;
151 } catch (Exception e) {
152 rollbackTransaction = true;
153 if (logger.isDebugEnabled()) {
154 logger.debug("Caught exception ", e);
156 throw DocumentException.createDocumentException(e);
158 ctx.setProperty(AuthorizationStore.ENTITY_MANAGER_PROP_KEY, null);
160 if (rollbackTransaction == true) {
161 if (em.getTransaction().isActive() == true) {
162 em.getTransaction().rollback();
165 // Don't call this unless "em" is not null -hence the check above.
166 JpaStorageUtils.releaseEntityManagerFactory(emf);
173 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.util.List, org.collectionspace.services.common.document.DocumentHandler)
176 public void get(ServiceContext ctx, List<String> csidList, DocumentHandler handler)
177 throws DocumentNotFoundException, DocumentException {
178 throw new UnsupportedOperationException();
182 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
185 public void get(ServiceContext ctx, String id, DocumentHandler handler)
186 throws DocumentNotFoundException, DocumentException {
188 throw new IllegalArgumentException(
189 "get: ctx is missing");
191 if (handler == null) {
192 throw new IllegalArgumentException(
193 "get: handler is missing");
195 EntityManagerFactory emf = null;
196 EntityManager em = null;
198 handler.prepare(Action.GET);
200 o = JpaStorageUtils.getEntity(getEntityName(ctx), id,
203 if (em != null && em.getTransaction().isActive()) {
204 em.getTransaction().rollback();
206 String msg = "could not find entity with id=" + id;
207 throw new DocumentNotFoundException(msg);
209 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(o);
210 handler.handle(Action.GET, wrapDoc);
211 handler.complete(Action.GET, wrapDoc);
212 } catch (DocumentException de) {
214 } catch (Exception e) {
215 if (logger.isDebugEnabled()) {
216 logger.debug("Caught exception ", e);
218 throw new DocumentException(e);
221 JpaStorageUtils.releaseEntityManagerFactory(emf);
227 * @see org.collectionspace.services.common.storage.StorageClient#getAll(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
230 public void getAll(ServiceContext ctx, DocumentHandler handler)
231 throws DocumentNotFoundException, DocumentException {
232 throw new UnsupportedOperationException("use getFiltered instead");
236 * @see org.collectionspace.services.common.storage.StorageClient#getFiltered(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
239 public void getFiltered(ServiceContext ctx, DocumentHandler handler)
240 throws DocumentNotFoundException, DocumentException {
241 QueryContext queryContext = new QueryContext(ctx, handler);
243 DocumentFilter docFilter = handler.getDocumentFilter();
244 if (docFilter == null) {
245 docFilter = handler.createDocumentFilter();
247 EntityManagerFactory emf = null;
248 EntityManager em = null;
250 handler.prepare(Action.GET_ALL);
251 StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
252 queryStrBldr.append(getEntityName(ctx));
253 queryStrBldr.append(" a");
255 List<DocumentFilter.ParamBinding> params = docFilter.buildWhereForSearch(queryStrBldr);
256 emf = JpaStorageUtils.getEntityManagerFactory();
257 em = emf.createEntityManager();
258 String queryStr = queryStrBldr.toString(); //for debugging
259 Query q = em.createQuery(queryStr);
261 for (DocumentFilter.ParamBinding p : params) {
262 q.setParameter(p.getName(), p.getValue());
264 if (docFilter.getOffset() > 0) {
265 q.setFirstResult(docFilter.getOffset());
267 if (docFilter.getPageSize() > 0) {
268 q.setMaxResults(docFilter.getPageSize());
271 //FIXME is transaction required for get?
272 em.getTransaction().begin();
273 List list = q.getResultList();
274 em.getTransaction().commit();
275 DocumentWrapper<List> wrapDoc = new DocumentWrapperImpl<List>(list);
276 handler.handle(Action.GET_ALL, wrapDoc);
277 handler.complete(Action.GET_ALL, wrapDoc);
278 } catch (DocumentException de) {
280 } catch (Exception e) {
281 if (logger.isDebugEnabled()) {
282 logger.debug("Caught exception ", e);
284 throw new DocumentException(e);
287 JpaStorageUtils.releaseEntityManagerFactory(emf);
293 * @see org.collectionspace.services.common.storage.StorageClient#update(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
296 public void update(ServiceContext ctx, String id, DocumentHandler handler)
297 throws BadRequestException, DocumentNotFoundException,
300 throw new IllegalArgumentException(
301 "update: ctx is missing");
303 if (handler == null) {
304 throw new IllegalArgumentException(
305 "update: handler is missing");
307 EntityManagerFactory emf = null;
308 EntityManager em = null;
310 handler.prepare(Action.UPDATE);
311 Object entityReceived = handler.getCommonPart();
312 emf = JpaStorageUtils.getEntityManagerFactory();
313 em = emf.createEntityManager();
314 em.getTransaction().begin();
315 Object entityFound = getEntity(em, id, entityReceived.getClass());
316 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entityFound);
317 handler.handle(Action.UPDATE, wrapDoc);
318 JaxbUtils.setValue(entityFound, "setUpdatedAtItem", Date.class, new Date());
319 em.getTransaction().commit();
320 handler.complete(Action.UPDATE, wrapDoc);
321 } catch (BadRequestException bre) {
322 if (em != null && em.getTransaction().isActive()) {
323 em.getTransaction().rollback();
326 } catch (DocumentException de) {
327 if (em != null && em.getTransaction().isActive()) {
328 em.getTransaction().rollback();
331 } catch (Exception e) {
332 if (logger.isDebugEnabled()) {
333 logger.debug("Caught exception ", e);
335 throw new DocumentException(e);
338 JpaStorageUtils.releaseEntityManagerFactory(emf);
344 * delete removes entity and its child entities
345 * cost: a get before delete
346 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
349 public void delete(ServiceContext ctx, String id)
350 throws DocumentNotFoundException,
353 if (logger.isDebugEnabled()) {
354 logger.debug("delete(ctx, id): deleting entity with id=" + id);
358 throw new IllegalArgumentException(
359 "delete(ctx, id): ctx is missing");
361 EntityManagerFactory emf = null;
362 EntityManager em = null;
365 emf = JpaStorageUtils.getEntityManagerFactory();
366 em = emf.createEntityManager();
368 em.getTransaction().begin();
369 Object entityFound = getEntity(ctx, em, id);
370 if (entityFound == null) {
371 if (em != null && em.getTransaction().isActive()) {
372 em.getTransaction().rollback();
374 String msg = "delete(ctx, id): could not find entity with id=" + id;
376 throw new DocumentNotFoundException(msg);
378 em.remove(entityFound);
379 em.getTransaction().commit();
381 } catch (DocumentException de) {
382 if (em != null && em.getTransaction().isActive()) {
383 em.getTransaction().rollback();
386 } catch (Exception e) {
387 if (logger.isDebugEnabled()) {
388 logger.debug("delete(ctx, id): Caught exception ", e);
390 if (em != null && em.getTransaction().isActive()) {
391 em.getTransaction().rollback();
393 throw new DocumentException(e);
396 JpaStorageUtils.releaseEntityManagerFactory(emf);
402 * deleteWhere uses the where clause to delete an entityReceived represented by the csidReceived
403 * it does not delete any child entities.
406 * @throws DocumentNotFoundException
407 * @throws DocumentException
409 public void deleteWhere(ServiceContext ctx, String id)
410 throws DocumentNotFoundException,
414 throw new IllegalArgumentException(
415 "deleteWhere(ctx, id) : ctx is missing");
418 if (logger.isDebugEnabled()) {
419 logger.debug("deleteWhere(ctx, id): deleting entity with id=" + id);
421 EntityManagerFactory emf = null;
422 EntityManager em = null;
424 StringBuilder deleteStr = new StringBuilder("DELETE FROM ");
425 deleteStr.append(getEntityName(ctx));
426 deleteStr.append(" WHERE csid = :csid and tenantId = :tenantId");
427 //TODO: add tenant csidReceived
429 emf = JpaStorageUtils.getEntityManagerFactory();
430 em = emf.createEntityManager();
431 Query q = em.createQuery(deleteStr.toString());
432 q.setParameter("csid", id);
433 q.setParameter("tenantId", ctx.getTenantId());
436 em.getTransaction().begin();
437 rcount = q.executeUpdate();
439 if (em != null && em.getTransaction().isActive()) {
440 em.getTransaction().rollback();
442 String msg = "deleteWhere(ctx, id) could not find entity with id=" + id;
444 throw new DocumentNotFoundException(msg);
446 em.getTransaction().commit();
448 } catch (DocumentException de) {
449 if (em != null && em.getTransaction().isActive()) {
450 em.getTransaction().rollback();
453 } catch (Exception e) {
454 if (logger.isDebugEnabled()) {
455 logger.debug("deleteWhere(ctx, id) Caught exception ", e);
457 if (em != null && em.getTransaction().isActive()) {
458 em.getTransaction().rollback();
460 throw new DocumentException(e);
463 JpaStorageUtils.releaseEntityManagerFactory(emf);
469 * delete removes entity and its child entities but calls back to given handler
470 * cost: a get before delete
471 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
474 public boolean delete(ServiceContext ctx, String id, DocumentHandler handler)
475 throws DocumentNotFoundException, DocumentException {
476 boolean result = true;
479 throw new IllegalArgumentException(
480 "delete(ctx, ix, handler): ctx is missing");
482 if (handler == null) {
483 throw new IllegalArgumentException(
484 "delete(ctx, ix, handler): handler is missing");
487 EntityManagerFactory emf = null;
488 EntityManager em = null;
490 handler.prepare(Action.DELETE);
492 emf = JpaStorageUtils.getEntityManagerFactory();
493 em = emf.createEntityManager();
495 em.getTransaction().begin();
496 Object entityFound = getEntity(ctx, em, id);
497 if (entityFound == null) {
498 if (em != null && em.getTransaction().isActive()) {
499 em.getTransaction().rollback();
501 String msg = "delete(ctx, ix, handler) could not find entity with id=" + id;
503 throw new DocumentNotFoundException(msg);
505 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entityFound);
506 handler.handle(Action.DELETE, wrapDoc);
507 em.remove(entityFound);
508 em.getTransaction().commit();
509 handler.complete(Action.DELETE, wrapDoc);
510 } catch (DocumentException de) {
511 if (em != null && em.getTransaction().isActive()) {
512 em.getTransaction().rollback();
515 } catch (Exception e) {
516 if (logger.isDebugEnabled()) {
517 logger.debug("delete(ctx, ix, handler): Caught exception ", e);
519 if (em != null && em.getTransaction().isActive()) {
520 em.getTransaction().rollback();
522 throw new DocumentException(e);
525 JpaStorageUtils.releaseEntityManagerFactory(emf);
533 * Gets the entityReceived name.
537 * @return the entityReceived name
539 protected String getEntityName(ServiceContext ctx) {
540 Object o = ctx.getProperty(ServiceContextProperties.ENTITY_NAME);
542 throw new IllegalArgumentException(ServiceContextProperties.ENTITY_NAME
543 + "property is missing in context "
551 * getEntity returns persistent entity for given id. it assumes that
552 * service context has property ServiceContextProperties.ENTITY_CLASS set
553 * rolls back the transaction if not found
554 * @param ctx service context
555 * @param em entity manager
556 * @param csid received
558 * @throws DocumentNotFoundException and rollsback the transaction if active
560 protected Object getEntity(ServiceContext ctx, EntityManager em, String id)
561 throws DocumentNotFoundException {
562 Class entityClazz = (Class) ctx.getProperty(ServiceContextProperties.ENTITY_CLASS);
563 if (entityClazz == null) {
564 String msg = ServiceContextProperties.ENTITY_CLASS
565 + " property is missing in the context";
567 throw new IllegalArgumentException(msg);
569 return getEntity(em, id, entityClazz);
573 * getEntity retrieves the persistent entity of given class for given id
574 * rolls back the transaction if not found
576 * @param id entity id
579 * @throws DocumentNotFoundException and rollsback the transaction if active
581 protected Object getEntity(EntityManager em, String id, Class entityClazz)
582 throws DocumentNotFoundException {
583 Object entityFound = JpaStorageUtils.getEntity(em, id, entityClazz);
584 if (entityFound == null) {
585 if (em != null && em.getTransaction().isActive()) {
586 em.getTransaction().rollback();
588 String msg = "could not find entity of type=" + entityClazz.getName()
591 throw new DocumentNotFoundException(msg);
597 public void get(ServiceContext ctx, DocumentHandler handler)
598 throws DocumentNotFoundException, DocumentException {
599 throw new UnsupportedOperationException();
603 public void doWorkflowTransition(ServiceContext ctx, String id,
604 DocumentHandler handler, TransitionDef transitionDef)
605 throws BadRequestException, DocumentNotFoundException,
607 // Do nothing. JPA services do not support workflow.
611 public void deleteWithWhereClause(ServiceContext ctx, String whereClause,
612 DocumentHandler handler) throws DocumentNotFoundException,
614 throw new UnsupportedOperationException();
618 public boolean synchronize(ServiceContext ctx, Object specifier,
619 DocumentHandler handler) throws DocumentNotFoundException,
620 TransactionException, DocumentException {
621 // TODO Auto-generated method stub
622 // Do nothing. Subclasses can override if they want/need to.
627 public boolean synchronizeItem(ServiceContext ctx, AuthorityItemSpecifier itemSpecifier,
628 DocumentHandler handler) throws DocumentNotFoundException,
629 TransactionException, DocumentException {
630 // TODO Auto-generated method stub
631 // Do nothing. Subclasses can override if they want/need to.