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.
138 // An entity's document handler class will throw this exception only if attempting to create (but not actually creating) duplicate is ok -e.g., Permission records.
140 entity = wrapDoc.getWrappedObject(); // the handler should have reset the wrapped transient object with the existing persisted entity we just found.
143 em.getTransaction().commit();
144 handler.complete(Action.CREATE, wrapDoc);
145 return (String) JaxbUtils.getValue(entity, "getCsid");
146 } catch (BadRequestException bre) {
147 rollbackTransaction = true;
149 } catch (DocumentException de) {
150 rollbackTransaction = true;
152 } catch (Exception e) {
153 rollbackTransaction = true;
154 if (logger.isDebugEnabled()) {
155 logger.debug("Caught exception ", e);
157 throw DocumentException.createDocumentException(e);
159 ctx.setProperty(AuthorizationStore.ENTITY_MANAGER_PROP_KEY, null);
161 if (rollbackTransaction == true) {
162 if (em.getTransaction().isActive() == true) {
163 em.getTransaction().rollback();
166 // Don't call this unless "em" is not null -hence the check above.
167 JpaStorageUtils.releaseEntityManagerFactory(emf);
174 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.util.List, org.collectionspace.services.common.document.DocumentHandler)
177 public void get(ServiceContext ctx, List<String> csidList, DocumentHandler handler)
178 throws DocumentNotFoundException, DocumentException {
179 throw new UnsupportedOperationException();
183 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
186 public void get(ServiceContext ctx, String id, DocumentHandler handler)
187 throws DocumentNotFoundException, DocumentException {
189 throw new IllegalArgumentException(
190 "get: ctx is missing");
192 if (handler == null) {
193 throw new IllegalArgumentException(
194 "get: handler is missing");
196 EntityManagerFactory emf = null;
197 EntityManager em = null;
199 handler.prepare(Action.GET);
201 o = JpaStorageUtils.getEntity(getEntityName(ctx), id,
204 if (em != null && em.getTransaction().isActive()) {
205 em.getTransaction().rollback();
207 String msg = "could not find entity with id=" + id;
208 throw new DocumentNotFoundException(msg);
210 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(o);
211 handler.handle(Action.GET, wrapDoc);
212 handler.complete(Action.GET, wrapDoc);
213 } catch (DocumentException de) {
215 } catch (Exception e) {
216 if (logger.isDebugEnabled()) {
217 logger.debug("Caught exception ", e);
219 throw new DocumentException(e);
222 JpaStorageUtils.releaseEntityManagerFactory(emf);
228 * @see org.collectionspace.services.common.storage.StorageClient#getAll(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
231 public void getAll(ServiceContext ctx, DocumentHandler handler)
232 throws DocumentNotFoundException, DocumentException {
233 throw new UnsupportedOperationException("use getFiltered instead");
237 * @see org.collectionspace.services.common.storage.StorageClient#getFiltered(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
240 public void getFiltered(ServiceContext ctx, DocumentHandler handler)
241 throws DocumentNotFoundException, DocumentException {
242 QueryContext queryContext = new QueryContext(ctx, handler);
244 DocumentFilter docFilter = handler.getDocumentFilter();
245 if (docFilter == null) {
246 docFilter = handler.createDocumentFilter();
248 EntityManagerFactory emf = null;
249 EntityManager em = null;
251 handler.prepare(Action.GET_ALL);
252 StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
253 queryStrBldr.append(getEntityName(ctx));
254 queryStrBldr.append(" a");
256 List<DocumentFilter.ParamBinding> params = docFilter.buildWhereForSearch(queryStrBldr);
257 emf = JpaStorageUtils.getEntityManagerFactory();
258 em = emf.createEntityManager();
259 String queryStr = queryStrBldr.toString(); //for debugging
260 Query q = em.createQuery(queryStr);
262 for (DocumentFilter.ParamBinding p : params) {
263 q.setParameter(p.getName(), p.getValue());
265 if (docFilter.getOffset() > 0) {
266 q.setFirstResult(docFilter.getOffset());
268 if (docFilter.getPageSize() > 0) {
269 q.setMaxResults(docFilter.getPageSize());
272 //FIXME is transaction required for get?
273 em.getTransaction().begin();
274 List list = q.getResultList();
275 em.getTransaction().commit();
276 DocumentWrapper<List> wrapDoc = new DocumentWrapperImpl<List>(list);
277 handler.handle(Action.GET_ALL, wrapDoc);
278 handler.complete(Action.GET_ALL, wrapDoc);
279 } catch (DocumentException de) {
281 } catch (Exception e) {
282 if (logger.isDebugEnabled()) {
283 logger.debug("Caught exception ", e);
285 throw new DocumentException(e);
288 JpaStorageUtils.releaseEntityManagerFactory(emf);
294 * @see org.collectionspace.services.common.storage.StorageClient#update(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
297 public void update(ServiceContext ctx, String id, DocumentHandler handler)
298 throws BadRequestException, DocumentNotFoundException,
301 throw new IllegalArgumentException(
302 "update: ctx is missing");
304 if (handler == null) {
305 throw new IllegalArgumentException(
306 "update: handler is missing");
308 EntityManagerFactory emf = null;
309 EntityManager em = null;
311 handler.prepare(Action.UPDATE);
312 Object entityReceived = handler.getCommonPart();
313 emf = JpaStorageUtils.getEntityManagerFactory();
314 em = emf.createEntityManager();
315 em.getTransaction().begin();
316 Object entityFound = getEntity(em, id, entityReceived.getClass());
317 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entityFound);
318 handler.handle(Action.UPDATE, wrapDoc);
319 JaxbUtils.setValue(entityFound, "setUpdatedAtItem", Date.class, new Date());
320 em.getTransaction().commit();
321 handler.complete(Action.UPDATE, wrapDoc);
322 } catch (BadRequestException bre) {
323 if (em != null && em.getTransaction().isActive()) {
324 em.getTransaction().rollback();
327 } catch (DocumentException de) {
328 if (em != null && em.getTransaction().isActive()) {
329 em.getTransaction().rollback();
332 } catch (Exception e) {
333 if (logger.isDebugEnabled()) {
334 logger.debug("Caught exception ", e);
336 throw new DocumentException(e);
339 JpaStorageUtils.releaseEntityManagerFactory(emf);
345 * delete removes entity and its child entities
346 * cost: a get before delete
347 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
350 public void delete(ServiceContext ctx, String id)
351 throws DocumentNotFoundException,
354 if (logger.isDebugEnabled()) {
355 logger.debug("delete(ctx, id): deleting entity with id=" + id);
359 throw new IllegalArgumentException(
360 "delete(ctx, id): ctx is missing");
362 EntityManagerFactory emf = null;
363 EntityManager em = null;
366 emf = JpaStorageUtils.getEntityManagerFactory();
367 em = emf.createEntityManager();
369 em.getTransaction().begin();
370 Object entityFound = getEntity(ctx, em, id);
371 if (entityFound == null) {
372 if (em != null && em.getTransaction().isActive()) {
373 em.getTransaction().rollback();
375 String msg = "delete(ctx, id): could not find entity with id=" + id;
377 throw new DocumentNotFoundException(msg);
379 em.remove(entityFound);
380 em.getTransaction().commit();
382 } catch (DocumentException de) {
383 if (em != null && em.getTransaction().isActive()) {
384 em.getTransaction().rollback();
387 } catch (Exception e) {
388 if (logger.isDebugEnabled()) {
389 logger.debug("delete(ctx, id): Caught exception ", e);
391 if (em != null && em.getTransaction().isActive()) {
392 em.getTransaction().rollback();
394 throw new DocumentException(e);
397 JpaStorageUtils.releaseEntityManagerFactory(emf);
403 * deleteWhere uses the where clause to delete an entityReceived represented by the csidReceived
404 * it does not delete any child entities.
407 * @throws DocumentNotFoundException
408 * @throws DocumentException
410 public void deleteWhere(ServiceContext ctx, String id)
411 throws DocumentNotFoundException,
415 throw new IllegalArgumentException(
416 "deleteWhere(ctx, id) : ctx is missing");
419 if (logger.isDebugEnabled()) {
420 logger.debug("deleteWhere(ctx, id): deleting entity with id=" + id);
422 EntityManagerFactory emf = null;
423 EntityManager em = null;
425 StringBuilder deleteStr = new StringBuilder("DELETE FROM ");
426 deleteStr.append(getEntityName(ctx));
427 deleteStr.append(" WHERE csid = :csid and tenantId = :tenantId");
428 //TODO: add tenant csidReceived
430 emf = JpaStorageUtils.getEntityManagerFactory();
431 em = emf.createEntityManager();
432 Query q = em.createQuery(deleteStr.toString());
433 q.setParameter("csid", id);
434 q.setParameter("tenantId", ctx.getTenantId());
437 em.getTransaction().begin();
438 rcount = q.executeUpdate();
440 if (em != null && em.getTransaction().isActive()) {
441 em.getTransaction().rollback();
443 String msg = "deleteWhere(ctx, id) could not find entity with id=" + id;
445 throw new DocumentNotFoundException(msg);
447 em.getTransaction().commit();
449 } catch (DocumentException de) {
450 if (em != null && em.getTransaction().isActive()) {
451 em.getTransaction().rollback();
454 } catch (Exception e) {
455 if (logger.isDebugEnabled()) {
456 logger.debug("deleteWhere(ctx, id) Caught exception ", e);
458 if (em != null && em.getTransaction().isActive()) {
459 em.getTransaction().rollback();
461 throw new DocumentException(e);
464 JpaStorageUtils.releaseEntityManagerFactory(emf);
470 * delete removes entity and its child entities but calls back to given handler
471 * cost: a get before delete
472 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
475 public boolean delete(ServiceContext ctx, String id, DocumentHandler handler)
476 throws DocumentNotFoundException, DocumentException {
477 boolean result = true;
480 throw new IllegalArgumentException(
481 "delete(ctx, ix, handler): ctx is missing");
483 if (handler == null) {
484 throw new IllegalArgumentException(
485 "delete(ctx, ix, handler): handler is missing");
488 EntityManagerFactory emf = null;
489 EntityManager em = null;
491 handler.prepare(Action.DELETE);
493 emf = JpaStorageUtils.getEntityManagerFactory();
494 em = emf.createEntityManager();
496 em.getTransaction().begin();
497 Object entityFound = getEntity(ctx, em, id);
498 if (entityFound == null) {
499 if (em != null && em.getTransaction().isActive()) {
500 em.getTransaction().rollback();
502 String msg = "delete(ctx, ix, handler) could not find entity with id=" + id;
504 throw new DocumentNotFoundException(msg);
506 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entityFound);
507 handler.handle(Action.DELETE, wrapDoc);
508 em.remove(entityFound);
509 em.getTransaction().commit();
510 handler.complete(Action.DELETE, wrapDoc);
511 } catch (DocumentException de) {
512 if (em != null && em.getTransaction().isActive()) {
513 em.getTransaction().rollback();
516 } catch (Exception e) {
517 if (logger.isDebugEnabled()) {
518 logger.debug("delete(ctx, ix, handler): Caught exception ", e);
520 if (em != null && em.getTransaction().isActive()) {
521 em.getTransaction().rollback();
523 throw new DocumentException(e);
526 JpaStorageUtils.releaseEntityManagerFactory(emf);
534 * Gets the entityReceived name.
538 * @return the entityReceived name
540 protected String getEntityName(ServiceContext ctx) {
541 Object o = ctx.getProperty(ServiceContextProperties.ENTITY_NAME);
543 throw new IllegalArgumentException(ServiceContextProperties.ENTITY_NAME
544 + "property is missing in context "
552 * getEntity returns persistent entity for given id. it assumes that
553 * service context has property ServiceContextProperties.ENTITY_CLASS set
554 * rolls back the transaction if not found
555 * @param ctx service context
556 * @param em entity manager
557 * @param csid received
559 * @throws DocumentNotFoundException and rollsback the transaction if active
561 protected Object getEntity(ServiceContext ctx, EntityManager em, String id)
562 throws DocumentNotFoundException {
563 Class entityClazz = (Class) ctx.getProperty(ServiceContextProperties.ENTITY_CLASS);
564 if (entityClazz == null) {
565 String msg = ServiceContextProperties.ENTITY_CLASS
566 + " property is missing in the context";
568 throw new IllegalArgumentException(msg);
570 return getEntity(em, id, entityClazz);
574 * getEntity retrieves the persistent entity of given class for given id
575 * rolls back the transaction if not found
577 * @param id entity id
580 * @throws DocumentNotFoundException and rollsback the transaction if active
582 protected Object getEntity(EntityManager em, String id, Class entityClazz)
583 throws DocumentNotFoundException {
584 Object entityFound = JpaStorageUtils.getEntity(em, id, entityClazz);
585 if (entityFound == null) {
586 if (em != null && em.getTransaction().isActive()) {
587 em.getTransaction().rollback();
589 String msg = "could not find entity of type=" + entityClazz.getName()
592 throw new DocumentNotFoundException(msg);
598 public void get(ServiceContext ctx, DocumentHandler handler)
599 throws DocumentNotFoundException, DocumentException {
600 throw new UnsupportedOperationException();
604 public void doWorkflowTransition(ServiceContext ctx, String id,
605 DocumentHandler handler, TransitionDef transitionDef)
606 throws BadRequestException, DocumentNotFoundException,
608 // Do nothing. JPA services do not support workflow.
612 public void deleteWithWhereClause(ServiceContext ctx, String whereClause,
613 DocumentHandler handler) throws DocumentNotFoundException,
615 throw new UnsupportedOperationException();
619 public boolean synchronize(ServiceContext ctx, Object specifier,
620 DocumentHandler handler) throws DocumentNotFoundException,
621 TransactionException, DocumentException {
622 // TODO Auto-generated method stub
623 // Do nothing. Subclasses can override if they want/need to.
628 public boolean synchronizeItem(ServiceContext ctx, AuthorityItemSpecifier itemSpecifier,
629 DocumentHandler handler) throws DocumentNotFoundException,
630 TransactionException, DocumentException {
631 // TODO Auto-generated method stub
632 // Do nothing. Subclasses can override if they want/need to.