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.EntityManager;
23 import javax.persistence.EntityManagerFactory;
24 import javax.persistence.Query;
25 import org.collectionspace.services.common.context.ServiceContext;
26 import org.collectionspace.services.common.document.BadRequestException;
27 import org.collectionspace.services.common.document.DocumentException;
28 import org.collectionspace.services.common.document.DocumentFilter;
29 import org.collectionspace.services.common.document.DocumentHandler;
30 import org.collectionspace.services.common.document.DocumentNotFoundException;
31 import org.collectionspace.services.common.document.DocumentHandler.Action;
32 import org.collectionspace.services.common.document.DocumentWrapper;
33 import org.collectionspace.services.common.document.DocumentWrapperImpl;
34 import org.collectionspace.services.common.document.JaxbUtils;
35 import org.collectionspace.services.common.storage.StorageClient;
36 import org.collectionspace.services.common.context.ServiceContextProperties;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
41 * JpaStorageClient is used to perform CRUD operations on SQL storage using JPA.
42 * It uses @see DocumentHandler as IOHandler with the client.
43 * All the operations in this client are carried out under their own transactions.
44 * A call to any method would start and commit/rollback a transaction.
46 * Assumption: each persistent entityReceived has the following 3 attributes
47 <xs:element name="createdAt" type="xs:dateTime">
51 <orm:column name="created_at" nullable="false"/>
56 <xs:element name="updatedAt" type="xs:dateTime">
60 <orm:column name="updated_at" />
66 <xs:attribute name="csid" type="xs:string">
70 <orm:column name="csid" length="128" nullable="false"/>
76 * $LastChangedRevision: $ $LastChangedDate: $
78 public class JpaStorageClientImpl implements StorageClient {
81 private final Logger logger = LoggerFactory.getLogger(JpaStorageClientImpl.class);
84 * Instantiates a new jpa storage client.
86 public JpaStorageClientImpl() {
90 * @see org.collectionspace.services.common.storage.StorageClient#create(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
93 public String create(ServiceContext ctx,
94 DocumentHandler handler) throws BadRequestException,
98 throw new IllegalArgumentException(
99 "create: ctx is missing");
101 if (handler == null) {
102 throw new IllegalArgumentException(
103 "create: handler is missing");
105 EntityManagerFactory emf = null;
106 EntityManager em = null;
108 handler.prepare(Action.CREATE);
109 Object entity = handler.getCommonPart();
110 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entity);
111 handler.handle(Action.CREATE, wrapDoc);
112 JaxbUtils.setValue(entity, "setCreatedAtItem", Date.class, new Date());
113 emf = JpaStorageUtils.getEntityManagerFactory();
114 em = emf.createEntityManager();
115 em.getTransaction().begin();
117 em.getTransaction().commit();
118 handler.complete(Action.CREATE, wrapDoc);
119 return (String) JaxbUtils.getValue(entity, "getCsid");
120 } catch (BadRequestException bre) {
121 if (em != null && em.getTransaction().isActive()) {
122 em.getTransaction().rollback();
125 } catch (DocumentException de) {
126 if (em != null && em.getTransaction().isActive()) {
127 em.getTransaction().rollback();
130 } catch (Exception e) {
131 if (em != null && em.getTransaction().isActive()) {
132 em.getTransaction().rollback();
134 if (logger.isDebugEnabled()) {
135 logger.debug("Caught exception ", e);
137 throw new DocumentException(e);
140 JpaStorageUtils.releaseEntityManagerFactory(emf);
147 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.util.List, org.collectionspace.services.common.document.DocumentHandler)
150 public void get(ServiceContext ctx, List<String> csidList, DocumentHandler handler)
151 throws DocumentNotFoundException, DocumentException {
152 throw new UnsupportedOperationException();
156 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
159 public void get(ServiceContext ctx, String id, DocumentHandler handler)
160 throws DocumentNotFoundException, DocumentException {
162 throw new IllegalArgumentException(
163 "get: ctx is missing");
165 if (handler == null) {
166 throw new IllegalArgumentException(
167 "get: handler is missing");
169 EntityManagerFactory emf = null;
170 EntityManager em = null;
172 handler.prepare(Action.GET);
174 o = JpaStorageUtils.getEntity(getEntityName(ctx), id,
177 if (em != null && em.getTransaction().isActive()) {
178 em.getTransaction().rollback();
180 String msg = "could not find entity with id=" + id;
181 throw new DocumentNotFoundException(msg);
183 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(o);
184 handler.handle(Action.GET, wrapDoc);
185 handler.complete(Action.GET, wrapDoc);
186 } catch (DocumentException de) {
188 } catch (Exception e) {
189 if (logger.isDebugEnabled()) {
190 logger.debug("Caught exception ", e);
192 throw new DocumentException(e);
195 JpaStorageUtils.releaseEntityManagerFactory(emf);
201 * @see org.collectionspace.services.common.storage.StorageClient#getAll(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
204 public void getAll(ServiceContext ctx, DocumentHandler handler)
205 throws DocumentNotFoundException, DocumentException {
206 throw new UnsupportedOperationException("use getFiltered instead");
210 * @see org.collectionspace.services.common.storage.StorageClient#getFiltered(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
213 public void getFiltered(ServiceContext ctx, DocumentHandler handler)
214 throws DocumentNotFoundException, DocumentException {
216 throw new IllegalArgumentException(
217 "getFiltered: ctx is missing");
219 if (handler == null) {
220 throw new IllegalArgumentException(
221 "getFiltered: handler is missing");
224 DocumentFilter docFilter = handler.getDocumentFilter();
225 if (docFilter == null) {
226 docFilter = handler.createDocumentFilter();
228 EntityManagerFactory emf = null;
229 EntityManager em = null;
231 handler.prepare(Action.GET_ALL);
233 StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
234 queryStrBldr.append(getEntityName(ctx));
235 queryStrBldr.append(" a");
236 List<DocumentFilter.ParamBinding> params = docFilter.buildWhereForSearch(queryStrBldr);
237 emf = JpaStorageUtils.getEntityManagerFactory();
238 em = emf.createEntityManager();
239 String queryStr = queryStrBldr.toString(); //for debugging
240 Query q = em.createQuery(queryStr);
242 for (DocumentFilter.ParamBinding p : params) {
243 q.setParameter(p.getName(), p.getValue());
245 if (docFilter.getOffset() > 0) {
246 q.setFirstResult(docFilter.getOffset());
248 if (docFilter.getPageSize() > 0) {
249 q.setMaxResults(docFilter.getPageSize());
252 //FIXME is transaction required for get?
253 em.getTransaction().begin();
254 List list = q.getResultList();
255 em.getTransaction().commit();
256 DocumentWrapper<List> wrapDoc = new DocumentWrapperImpl<List>(list);
257 handler.handle(Action.GET_ALL, wrapDoc);
258 handler.complete(Action.GET_ALL, wrapDoc);
259 } catch (DocumentException de) {
261 } catch (Exception e) {
262 if (logger.isDebugEnabled()) {
263 logger.debug("Caught exception ", e);
265 throw new DocumentException(e);
268 JpaStorageUtils.releaseEntityManagerFactory(emf);
274 * @see org.collectionspace.services.common.storage.StorageClient#update(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
277 public void update(ServiceContext ctx, String id, DocumentHandler handler)
278 throws BadRequestException, DocumentNotFoundException,
281 throw new IllegalArgumentException(
282 "update: ctx is missing");
284 if (handler == null) {
285 throw new IllegalArgumentException(
286 "update: handler is missing");
288 EntityManagerFactory emf = null;
289 EntityManager em = null;
291 handler.prepare(Action.UPDATE);
292 Object entityReceived = handler.getCommonPart();
293 emf = JpaStorageUtils.getEntityManagerFactory();
294 em = emf.createEntityManager();
295 em.getTransaction().begin();
296 Object entityFound = getEntity(em, id, entityReceived.getClass());
297 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entityFound);
298 handler.handle(Action.UPDATE, wrapDoc);
299 JaxbUtils.setValue(entityFound, "setUpdatedAtItem", Date.class, new Date());
300 em.getTransaction().commit();
301 handler.complete(Action.UPDATE, wrapDoc);
302 } catch (BadRequestException bre) {
303 if (em != null && em.getTransaction().isActive()) {
304 em.getTransaction().rollback();
307 } catch (DocumentException de) {
308 if (em != null && em.getTransaction().isActive()) {
309 em.getTransaction().rollback();
312 } catch (Exception e) {
313 if (logger.isDebugEnabled()) {
314 logger.debug("Caught exception ", e);
316 throw new DocumentException(e);
319 JpaStorageUtils.releaseEntityManagerFactory(emf);
325 * delete removes entity and its child entities
326 * cost: a get before delete
327 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
330 public void delete(ServiceContext ctx, String id)
331 throws DocumentNotFoundException,
334 if (logger.isDebugEnabled()) {
335 logger.debug("delete(ctx, id): deleting entity with id=" + id);
339 throw new IllegalArgumentException(
340 "delete(ctx, id): ctx is missing");
342 EntityManagerFactory emf = null;
343 EntityManager em = null;
346 emf = JpaStorageUtils.getEntityManagerFactory();
347 em = emf.createEntityManager();
349 em.getTransaction().begin();
350 Object entityFound = getEntity(ctx, em, id);
351 if (entityFound == null) {
352 if (em != null && em.getTransaction().isActive()) {
353 em.getTransaction().rollback();
355 String msg = "delete(ctx, id): could not find entity with id=" + id;
357 throw new DocumentNotFoundException(msg);
359 em.remove(entityFound);
360 em.getTransaction().commit();
362 } catch (DocumentException de) {
363 if (em != null && em.getTransaction().isActive()) {
364 em.getTransaction().rollback();
367 } catch (Exception e) {
368 if (logger.isDebugEnabled()) {
369 logger.debug("delete(ctx, id): Caught exception ", e);
371 if (em != null && em.getTransaction().isActive()) {
372 em.getTransaction().rollback();
374 throw new DocumentException(e);
377 JpaStorageUtils.releaseEntityManagerFactory(emf);
383 * deleteWhere uses the where clause to delete an entityReceived represented by the csidReceived
384 * it does not delete any child entities.
387 * @throws DocumentNotFoundException
388 * @throws DocumentException
390 public void deleteWhere(ServiceContext ctx, String id)
391 throws DocumentNotFoundException,
395 throw new IllegalArgumentException(
396 "deleteWhere(ctx, id) : ctx is missing");
399 if (logger.isDebugEnabled()) {
400 logger.debug("deleteWhere(ctx, id): deleting entity with id=" + id);
402 EntityManagerFactory emf = null;
403 EntityManager em = null;
405 StringBuilder deleteStr = new StringBuilder("DELETE FROM ");
406 deleteStr.append(getEntityName(ctx));
407 deleteStr.append(" WHERE csid = :csid and tenantId = :tenantId");
408 //TODO: add tenant csidReceived
410 emf = JpaStorageUtils.getEntityManagerFactory();
411 em = emf.createEntityManager();
412 Query q = em.createQuery(deleteStr.toString());
413 q.setParameter("csid", id);
414 q.setParameter("tenantId", ctx.getTenantId());
417 em.getTransaction().begin();
418 rcount = q.executeUpdate();
420 if (em != null && em.getTransaction().isActive()) {
421 em.getTransaction().rollback();
423 String msg = "deleteWhere(ctx, id) could not find entity with id=" + id;
425 throw new DocumentNotFoundException(msg);
427 em.getTransaction().commit();
429 } catch (DocumentException de) {
430 if (em != null && em.getTransaction().isActive()) {
431 em.getTransaction().rollback();
434 } catch (Exception e) {
435 if (logger.isDebugEnabled()) {
436 logger.debug("deleteWhere(ctx, id) Caught exception ", e);
438 if (em != null && em.getTransaction().isActive()) {
439 em.getTransaction().rollback();
441 throw new DocumentException(e);
444 JpaStorageUtils.releaseEntityManagerFactory(emf);
450 * delete removes entity and its child entities but calls back to given handler
451 * cost: a get before delete
452 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
455 public void delete(ServiceContext ctx, String id, DocumentHandler handler)
456 throws DocumentNotFoundException, DocumentException {
458 throw new IllegalArgumentException(
459 "delete(ctx, ix, handler): ctx is missing");
461 if (handler == null) {
462 throw new IllegalArgumentException(
463 "delete(ctx, ix, handler): handler is missing");
465 EntityManagerFactory emf = null;
466 EntityManager em = null;
468 handler.prepare(Action.DELETE);
470 emf = JpaStorageUtils.getEntityManagerFactory();
471 em = emf.createEntityManager();
473 em.getTransaction().begin();
474 Object entityFound = getEntity(ctx, em, id);
475 if (entityFound == null) {
476 if (em != null && em.getTransaction().isActive()) {
477 em.getTransaction().rollback();
479 String msg = "delete(ctx, ix, handler) could not find entity with id=" + id;
481 throw new DocumentNotFoundException(msg);
483 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entityFound);
484 handler.handle(Action.DELETE, wrapDoc);
485 em.remove(entityFound);
486 em.getTransaction().commit();
488 handler.complete(Action.DELETE, wrapDoc);
489 } catch (DocumentException de) {
490 if (em != null && em.getTransaction().isActive()) {
491 em.getTransaction().rollback();
494 } catch (Exception e) {
495 if (logger.isDebugEnabled()) {
496 logger.debug("delete(ctx, ix, handler): Caught exception ", e);
498 if (em != null && em.getTransaction().isActive()) {
499 em.getTransaction().rollback();
501 throw new DocumentException(e);
504 JpaStorageUtils.releaseEntityManagerFactory(emf);
510 * Gets the entityReceived name.
514 * @return the entityReceived name
516 protected String getEntityName(ServiceContext ctx) {
517 Object o = ctx.getProperty(ServiceContextProperties.ENTITY_NAME);
519 throw new IllegalArgumentException(ServiceContextProperties.ENTITY_NAME
520 + "property is missing in context "
528 * getEntity returns persistent entity for given id. it assumes that
529 * service context has property ServiceContextProperties.ENTITY_CLASS set
530 * rolls back the transaction if not found
531 * @param ctx service context
532 * @param em entity manager
533 * @param csid received
535 * @throws DocumentNotFoundException and rollsback the transaction if active
537 protected Object getEntity(ServiceContext ctx, EntityManager em, String id)
538 throws DocumentNotFoundException {
539 Class entityClazz = (Class) ctx.getProperty(ServiceContextProperties.ENTITY_CLASS);
540 if (entityClazz == null) {
541 String msg = ServiceContextProperties.ENTITY_CLASS
542 + " property is missing in the context";
544 throw new IllegalArgumentException(msg);
546 return getEntity(em, id, entityClazz);
550 * getEntity retrieves the persistent entity of given class for given id
551 * rolls back the transaction if not found
553 * @param id entity id
556 * @throws DocumentNotFoundException and rollsback the transaction if active
558 protected Object getEntity(EntityManager em, String id, Class entityClazz)
559 throws DocumentNotFoundException {
560 Object entityFound = JpaStorageUtils.getEntity(em, id, entityClazz);
561 if (entityFound == null) {
562 if (em != null && em.getTransaction().isActive()) {
563 em.getTransaction().rollback();
565 String msg = "could not find entity of type=" + entityClazz.getName()
568 throw new DocumentNotFoundException(msg);
574 public void get(ServiceContext ctx, DocumentHandler handler)
575 throws DocumentNotFoundException, DocumentException {
576 throw new UnsupportedOperationException();