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.EntityManager;
24 import javax.persistence.EntityManagerFactory;
25 import javax.persistence.Query;
27 import org.collectionspace.services.common.document.BadRequestException;
28 import org.collectionspace.services.common.document.DocumentException;
29 import org.collectionspace.services.common.document.DocumentFilter;
30 import org.collectionspace.services.common.document.DocumentHandler;
31 import org.collectionspace.services.common.document.DocumentNotFoundException;
32 import org.collectionspace.services.common.document.DocumentHandler.Action;
33 import org.collectionspace.services.common.document.DocumentWrapper;
34 import org.collectionspace.services.common.document.DocumentWrapperImpl;
35 import org.collectionspace.services.common.document.JaxbUtils;
37 import org.collectionspace.services.common.storage.StorageClient;
38 import org.collectionspace.services.common.context.ServiceContextProperties;
39 import org.collectionspace.services.common.context.ServiceContext;
40 import org.collectionspace.services.common.query.QueryContext;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
46 * JpaStorageClient is used to perform CRUD operations on SQL storage using JPA.
47 * It uses @see DocumentHandler as IOHandler with the client.
48 * All the operations in this client are carried out under their own transactions.
49 * A call to any method would start and commit/rollback a transaction.
51 * Assumption: each persistent entityReceived has the following 3 attributes
52 <xs:element name="createdAt" type="xs:dateTime">
56 <orm:column name="created_at" nullable="false"/>
61 <xs:element name="updatedAt" type="xs:dateTime">
65 <orm:column name="updated_at" />
71 <xs:attribute name="csid" type="xs:string">
75 <orm:column name="csid" length="128" nullable="false"/>
81 * $LastChangedRevision: $ $LastChangedDate: $
83 public class JpaStorageClientImpl implements StorageClient {
86 private final Logger logger = LoggerFactory.getLogger(JpaStorageClientImpl.class);
89 * Instantiates a new jpa storage client.
91 public JpaStorageClientImpl() {
95 * @see org.collectionspace.services.common.storage.StorageClient#create(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
98 public String create(ServiceContext ctx,
99 DocumentHandler handler) throws BadRequestException,
103 throw new IllegalArgumentException(
104 "create: ctx is missing");
106 if (handler == null) {
107 throw new IllegalArgumentException(
108 "create: handler is missing");
110 EntityManagerFactory emf = null;
111 EntityManager em = null;
113 handler.prepare(Action.CREATE);
114 Object entity = handler.getCommonPart();
115 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entity);
116 handler.handle(Action.CREATE, wrapDoc);
117 JaxbUtils.setValue(entity, "setCreatedAtItem", Date.class, new Date());
118 emf = JpaStorageUtils.getEntityManagerFactory();
119 em = emf.createEntityManager();
120 em.getTransaction().begin();
122 em.getTransaction().commit();
123 handler.complete(Action.CREATE, wrapDoc);
124 return (String) JaxbUtils.getValue(entity, "getCsid");
125 } catch (BadRequestException bre) {
126 if (em != null && em.getTransaction().isActive()) {
127 em.getTransaction().rollback();
130 } catch (DocumentException de) {
131 if (em != null && em.getTransaction().isActive()) {
132 em.getTransaction().rollback();
135 } catch (Exception e) {
136 if (em != null && em.getTransaction().isActive()) {
137 em.getTransaction().rollback();
139 if (logger.isDebugEnabled()) {
140 logger.debug("Caught exception ", e);
142 throw new DocumentException(e);
145 JpaStorageUtils.releaseEntityManagerFactory(emf);
152 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.util.List, org.collectionspace.services.common.document.DocumentHandler)
155 public void get(ServiceContext ctx, List<String> csidList, DocumentHandler handler)
156 throws DocumentNotFoundException, DocumentException {
157 throw new UnsupportedOperationException();
161 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
164 public void get(ServiceContext ctx, String id, DocumentHandler handler)
165 throws DocumentNotFoundException, DocumentException {
167 throw new IllegalArgumentException(
168 "get: ctx is missing");
170 if (handler == null) {
171 throw new IllegalArgumentException(
172 "get: handler is missing");
174 EntityManagerFactory emf = null;
175 EntityManager em = null;
177 handler.prepare(Action.GET);
179 o = JpaStorageUtils.getEntity(getEntityName(ctx), id,
182 if (em != null && em.getTransaction().isActive()) {
183 em.getTransaction().rollback();
185 String msg = "could not find entity with id=" + id;
186 throw new DocumentNotFoundException(msg);
188 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(o);
189 handler.handle(Action.GET, wrapDoc);
190 handler.complete(Action.GET, wrapDoc);
191 } catch (DocumentException de) {
193 } catch (Exception e) {
194 if (logger.isDebugEnabled()) {
195 logger.debug("Caught exception ", e);
197 throw new DocumentException(e);
200 JpaStorageUtils.releaseEntityManagerFactory(emf);
206 * @see org.collectionspace.services.common.storage.StorageClient#getAll(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
209 public void getAll(ServiceContext ctx, DocumentHandler handler)
210 throws DocumentNotFoundException, DocumentException {
211 throw new UnsupportedOperationException("use getFiltered instead");
215 * @see org.collectionspace.services.common.storage.StorageClient#getFiltered(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
218 public void getFiltered(ServiceContext ctx, DocumentHandler handler)
219 throws DocumentNotFoundException, DocumentException {
220 QueryContext queryContext = new QueryContext(ctx, handler);
222 DocumentFilter docFilter = handler.getDocumentFilter();
223 if (docFilter == null) {
224 docFilter = handler.createDocumentFilter();
226 EntityManagerFactory emf = null;
227 EntityManager em = null;
229 handler.prepare(Action.GET_ALL);
230 StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
231 queryStrBldr.append(getEntityName(ctx));
232 queryStrBldr.append(" a");
234 List<DocumentFilter.ParamBinding> params = docFilter.buildWhereForSearch(queryStrBldr);
235 emf = JpaStorageUtils.getEntityManagerFactory();
236 em = emf.createEntityManager();
237 String queryStr = queryStrBldr.toString(); //for debugging
238 Query q = em.createQuery(queryStr);
240 for (DocumentFilter.ParamBinding p : params) {
241 q.setParameter(p.getName(), p.getValue());
243 if (docFilter.getOffset() > 0) {
244 q.setFirstResult(docFilter.getOffset());
246 if (docFilter.getPageSize() > 0) {
247 q.setMaxResults(docFilter.getPageSize());
250 //FIXME is transaction required for get?
251 em.getTransaction().begin();
252 List list = q.getResultList();
253 em.getTransaction().commit();
254 DocumentWrapper<List> wrapDoc = new DocumentWrapperImpl<List>(list);
255 handler.handle(Action.GET_ALL, wrapDoc);
256 handler.complete(Action.GET_ALL, wrapDoc);
257 } catch (DocumentException de) {
259 } catch (Exception e) {
260 if (logger.isDebugEnabled()) {
261 logger.debug("Caught exception ", e);
263 throw new DocumentException(e);
266 JpaStorageUtils.releaseEntityManagerFactory(emf);
272 * @see org.collectionspace.services.common.storage.StorageClient#update(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
275 public void update(ServiceContext ctx, String id, DocumentHandler handler)
276 throws BadRequestException, DocumentNotFoundException,
279 throw new IllegalArgumentException(
280 "update: ctx is missing");
282 if (handler == null) {
283 throw new IllegalArgumentException(
284 "update: handler is missing");
286 EntityManagerFactory emf = null;
287 EntityManager em = null;
289 handler.prepare(Action.UPDATE);
290 Object entityReceived = handler.getCommonPart();
291 emf = JpaStorageUtils.getEntityManagerFactory();
292 em = emf.createEntityManager();
293 em.getTransaction().begin();
294 Object entityFound = getEntity(em, id, entityReceived.getClass());
295 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entityFound);
296 handler.handle(Action.UPDATE, wrapDoc);
297 JaxbUtils.setValue(entityFound, "setUpdatedAtItem", Date.class, new Date());
298 em.getTransaction().commit();
299 handler.complete(Action.UPDATE, wrapDoc);
300 } catch (BadRequestException bre) {
301 if (em != null && em.getTransaction().isActive()) {
302 em.getTransaction().rollback();
305 } catch (DocumentException de) {
306 if (em != null && em.getTransaction().isActive()) {
307 em.getTransaction().rollback();
310 } catch (Exception e) {
311 if (logger.isDebugEnabled()) {
312 logger.debug("Caught exception ", e);
314 throw new DocumentException(e);
317 JpaStorageUtils.releaseEntityManagerFactory(emf);
323 * delete removes entity and its child entities
324 * cost: a get before delete
325 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
328 public void delete(ServiceContext ctx, String id)
329 throws DocumentNotFoundException,
332 if (logger.isDebugEnabled()) {
333 logger.debug("delete(ctx, id): deleting entity with id=" + id);
337 throw new IllegalArgumentException(
338 "delete(ctx, id): ctx is missing");
340 EntityManagerFactory emf = null;
341 EntityManager em = null;
344 emf = JpaStorageUtils.getEntityManagerFactory();
345 em = emf.createEntityManager();
347 em.getTransaction().begin();
348 Object entityFound = getEntity(ctx, em, id);
349 if (entityFound == null) {
350 if (em != null && em.getTransaction().isActive()) {
351 em.getTransaction().rollback();
353 String msg = "delete(ctx, id): could not find entity with id=" + id;
355 throw new DocumentNotFoundException(msg);
357 em.remove(entityFound);
358 em.getTransaction().commit();
360 } catch (DocumentException de) {
361 if (em != null && em.getTransaction().isActive()) {
362 em.getTransaction().rollback();
365 } catch (Exception e) {
366 if (logger.isDebugEnabled()) {
367 logger.debug("delete(ctx, id): Caught exception ", e);
369 if (em != null && em.getTransaction().isActive()) {
370 em.getTransaction().rollback();
372 throw new DocumentException(e);
375 JpaStorageUtils.releaseEntityManagerFactory(emf);
381 * deleteWhere uses the where clause to delete an entityReceived represented by the csidReceived
382 * it does not delete any child entities.
385 * @throws DocumentNotFoundException
386 * @throws DocumentException
388 public void deleteWhere(ServiceContext ctx, String id)
389 throws DocumentNotFoundException,
393 throw new IllegalArgumentException(
394 "deleteWhere(ctx, id) : ctx is missing");
397 if (logger.isDebugEnabled()) {
398 logger.debug("deleteWhere(ctx, id): deleting entity with id=" + id);
400 EntityManagerFactory emf = null;
401 EntityManager em = null;
403 StringBuilder deleteStr = new StringBuilder("DELETE FROM ");
404 deleteStr.append(getEntityName(ctx));
405 deleteStr.append(" WHERE csid = :csid and tenantId = :tenantId");
406 //TODO: add tenant csidReceived
408 emf = JpaStorageUtils.getEntityManagerFactory();
409 em = emf.createEntityManager();
410 Query q = em.createQuery(deleteStr.toString());
411 q.setParameter("csid", id);
412 q.setParameter("tenantId", ctx.getTenantId());
415 em.getTransaction().begin();
416 rcount = q.executeUpdate();
418 if (em != null && em.getTransaction().isActive()) {
419 em.getTransaction().rollback();
421 String msg = "deleteWhere(ctx, id) could not find entity with id=" + id;
423 throw new DocumentNotFoundException(msg);
425 em.getTransaction().commit();
427 } catch (DocumentException de) {
428 if (em != null && em.getTransaction().isActive()) {
429 em.getTransaction().rollback();
432 } catch (Exception e) {
433 if (logger.isDebugEnabled()) {
434 logger.debug("deleteWhere(ctx, id) Caught exception ", e);
436 if (em != null && em.getTransaction().isActive()) {
437 em.getTransaction().rollback();
439 throw new DocumentException(e);
442 JpaStorageUtils.releaseEntityManagerFactory(emf);
448 * delete removes entity and its child entities but calls back to given handler
449 * cost: a get before delete
450 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
453 public void delete(ServiceContext ctx, String id, DocumentHandler handler)
454 throws DocumentNotFoundException, DocumentException {
456 throw new IllegalArgumentException(
457 "delete(ctx, ix, handler): ctx is missing");
459 if (handler == null) {
460 throw new IllegalArgumentException(
461 "delete(ctx, ix, handler): handler is missing");
463 EntityManagerFactory emf = null;
464 EntityManager em = null;
466 handler.prepare(Action.DELETE);
468 emf = JpaStorageUtils.getEntityManagerFactory();
469 em = emf.createEntityManager();
471 em.getTransaction().begin();
472 Object entityFound = getEntity(ctx, em, id);
473 if (entityFound == null) {
474 if (em != null && em.getTransaction().isActive()) {
475 em.getTransaction().rollback();
477 String msg = "delete(ctx, ix, handler) could not find entity with id=" + id;
479 throw new DocumentNotFoundException(msg);
481 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entityFound);
482 handler.handle(Action.DELETE, wrapDoc);
483 em.remove(entityFound);
484 em.getTransaction().commit();
486 handler.complete(Action.DELETE, wrapDoc);
487 } catch (DocumentException de) {
488 if (em != null && em.getTransaction().isActive()) {
489 em.getTransaction().rollback();
492 } catch (Exception e) {
493 if (logger.isDebugEnabled()) {
494 logger.debug("delete(ctx, ix, handler): Caught exception ", e);
496 if (em != null && em.getTransaction().isActive()) {
497 em.getTransaction().rollback();
499 throw new DocumentException(e);
502 JpaStorageUtils.releaseEntityManagerFactory(emf);
508 * Gets the entityReceived name.
512 * @return the entityReceived name
514 protected String getEntityName(ServiceContext ctx) {
515 Object o = ctx.getProperty(ServiceContextProperties.ENTITY_NAME);
517 throw new IllegalArgumentException(ServiceContextProperties.ENTITY_NAME
518 + "property is missing in context "
526 * getEntity returns persistent entity for given id. it assumes that
527 * service context has property ServiceContextProperties.ENTITY_CLASS set
528 * rolls back the transaction if not found
529 * @param ctx service context
530 * @param em entity manager
531 * @param csid received
533 * @throws DocumentNotFoundException and rollsback the transaction if active
535 protected Object getEntity(ServiceContext ctx, EntityManager em, String id)
536 throws DocumentNotFoundException {
537 Class entityClazz = (Class) ctx.getProperty(ServiceContextProperties.ENTITY_CLASS);
538 if (entityClazz == null) {
539 String msg = ServiceContextProperties.ENTITY_CLASS
540 + " property is missing in the context";
542 throw new IllegalArgumentException(msg);
544 return getEntity(em, id, entityClazz);
548 * getEntity retrieves the persistent entity of given class for given id
549 * rolls back the transaction if not found
551 * @param id entity id
554 * @throws DocumentNotFoundException and rollsback the transaction if active
556 protected Object getEntity(EntityManager em, String id, Class entityClazz)
557 throws DocumentNotFoundException {
558 Object entityFound = JpaStorageUtils.getEntity(em, id, entityClazz);
559 if (entityFound == null) {
560 if (em != null && em.getTransaction().isActive()) {
561 em.getTransaction().rollback();
563 String msg = "could not find entity of type=" + entityClazz.getName()
566 throw new DocumentNotFoundException(msg);
572 public void get(ServiceContext ctx, DocumentHandler handler)
573 throws DocumentNotFoundException, DocumentException {
574 throw new UnsupportedOperationException();