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.lang.reflect.InvocationTargetException;
21 import java.lang.reflect.Method;
22 import java.util.Date;
23 import java.util.List;
24 import javax.persistence.EntityManager;
25 import javax.persistence.EntityManagerFactory;
26 import javax.persistence.NoResultException;
27 import javax.persistence.Persistence;
28 import javax.persistence.Query;
29 import org.collectionspace.services.common.context.ServiceContext;
30 import org.collectionspace.services.common.document.BadRequestException;
31 import org.collectionspace.services.common.document.DocumentException;
32 import org.collectionspace.services.common.document.DocumentFilter;
33 import org.collectionspace.services.common.document.DocumentHandler;
34 import org.collectionspace.services.common.document.DocumentNotFoundException;
35 import org.collectionspace.services.common.document.DocumentHandler.Action;
36 import org.collectionspace.services.common.document.DocumentWrapper;
37 import org.collectionspace.services.common.document.DocumentWrapperImpl;
38 import org.collectionspace.services.common.storage.StorageClient;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
43 * JpaStorageClient is used to perform CRUD operations on SQL storage using JPA.
44 * It uses @see DocumentHandler as IOHandler with the client.
45 * All the operations in this client are carried out under their own transactions.
46 * A call to any method would start and commit/rollback a transaction.
48 * Assumption: each persistent entity has the following 3 attributes
49 <xs:element name="createdAt" type="xs:dateTime">
53 <orm:column name="created_at" nullable="false"/>
58 <xs:element name="updatedAt" type="xs:dateTime">
62 <orm:column name="updated_at" />
68 <xs:attribute name="csid" type="xs:string">
72 <orm:column name="csid" length="128" nullable="false"/>
78 * $LastChangedRevision: $ $LastChangedDate: $
80 public class JpaStorageClientImpl implements StorageClient {
83 private final Logger logger = LoggerFactory.getLogger(JpaStorageClientImpl.class);
84 /** The Constant CS_PERSISTENCE_UNIT. */
85 public final static String CS_PERSISTENCE_UNIT = "org.collectionspace.services";
86 private Class entityClazz;
89 * Instantiates a new jpa storage client.
91 public JpaStorageClientImpl() {
94 public JpaStorageClientImpl(Class entityClazz) {
95 this.entityClazz = entityClazz;
99 * @see org.collectionspace.services.common.storage.StorageClient#create(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
102 public String create(ServiceContext ctx,
103 DocumentHandler handler) throws BadRequestException,
107 throw new IllegalArgumentException(
108 "JpaStorageClient.create: ctx is missing");
110 if (handler == null) {
111 throw new IllegalArgumentException(
112 "JpaStorageClient.create: handler is missing");
114 EntityManagerFactory emf = null;
115 EntityManager em = null;
117 handler.prepare(Action.CREATE);
118 Object entity = handler.getCommonPart();
119 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entity);
120 handler.handle(Action.CREATE, wrapDoc);
121 setValue(entity, "setCreatedAtItem", Date.class, new Date());
122 emf = getEntityManagerFactory();
123 em = emf.createEntityManager();
124 em.getTransaction().begin();
126 em.getTransaction().commit();
127 handler.complete(Action.CREATE, wrapDoc);
128 return (String) getValue(entity, "getCsid");
129 } catch (BadRequestException bre) {
130 if (em != null && em.getTransaction().isActive()) {
131 em.getTransaction().rollback();
134 } catch (DocumentException de) {
136 } catch (Exception e) {
137 if (em != null && em.getTransaction().isActive()) {
138 em.getTransaction().rollback();
140 if (logger.isDebugEnabled()) {
141 logger.debug("Caught exception ", e);
143 throw new DocumentException(e);
146 releaseEntityManagerFactory(emf);
153 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.util.List, org.collectionspace.services.common.document.DocumentHandler)
156 public void get(ServiceContext ctx, List<String> csidList, DocumentHandler handler)
157 throws DocumentNotFoundException, DocumentException {
158 throw new UnsupportedOperationException();
162 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
165 public void get(ServiceContext ctx, String id, DocumentHandler handler)
166 throws DocumentNotFoundException, DocumentException {
168 throw new IllegalArgumentException(
169 "JpaStorageClient.get: ctx is missing");
171 if (handler == null) {
172 throw new IllegalArgumentException(
173 "JpaStorageClient.get: handler is missing");
175 DocumentFilter docFilter = handler.getDocumentFilter();
176 if (docFilter == null) {
177 docFilter = handler.createDocumentFilter();
179 EntityManagerFactory emf = null;
180 EntityManager em = null;
182 handler.prepare(Action.GET);
183 StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
184 queryStrBldr.append(getEntityName(ctx));
185 queryStrBldr.append(" a");
186 queryStrBldr.append(" WHERE csid = :csid");
187 //TODO: add tenant id
188 String where = docFilter.getWhereClause();
189 if ((null != where) && (where.length() > 0)) {
190 queryStrBldr.append(" AND " + where);
192 emf = getEntityManagerFactory();
193 em = emf.createEntityManager();
194 String queryStr = queryStrBldr.toString(); //for debugging
195 Query q = em.createQuery(queryStr);
196 q.setParameter("csid", id);
197 //TODO: add tenant id
200 if ((docFilter.getOffset() > 0) || (docFilter.getPageSize() > 0)) {
206 //require transaction for get?
207 em.getTransaction().begin();
208 o = q.getSingleResult();
209 em.getTransaction().commit();
210 } catch (NoResultException nre) {
211 if (em != null && em.getTransaction().isActive()) {
212 em.getTransaction().rollback();
214 String msg = "could not find entity with id=" + id;
215 logger.error(msg, nre);
216 throw new DocumentNotFoundException(msg, nre);
218 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(o);
219 handler.handle(Action.GET, wrapDoc);
220 handler.complete(Action.GET, wrapDoc);
221 } catch (DocumentException de) {
223 } catch (Exception e) {
224 if (logger.isDebugEnabled()) {
225 logger.debug("Caught exception ", e);
227 throw new DocumentException(e);
230 releaseEntityManagerFactory(emf);
236 * @see org.collectionspace.services.common.storage.StorageClient#getAll(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
239 public void getAll(ServiceContext ctx, DocumentHandler handler)
240 throws DocumentNotFoundException, DocumentException {
241 throw new UnsupportedOperationException("use getFiltered instead");
245 * @see org.collectionspace.services.common.storage.StorageClient#getFiltered(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
248 public void getFiltered(ServiceContext ctx, DocumentHandler handler)
249 throws DocumentNotFoundException, DocumentException {
251 throw new IllegalArgumentException(
252 "JpaStorageClient.getFiltered: ctx is missing");
254 if (handler == null) {
255 throw new IllegalArgumentException(
256 "JpaStorageClient.getFiltered: handler is missing");
258 DocumentFilter docFilter = handler.getDocumentFilter();
259 if (docFilter == null) {
260 docFilter = handler.createDocumentFilter();
262 EntityManagerFactory emf = null;
263 EntityManager em = null;
265 handler.prepare(Action.GET_ALL);
267 StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
268 queryStrBldr.append(getEntityName(ctx));
269 queryStrBldr.append(" a");
270 List<DocumentFilter.ParamBinding> params = docFilter.buildWhereForSearch(queryStrBldr);
271 //TODO: add tenant id
272 emf = getEntityManagerFactory();
273 em = emf.createEntityManager();
274 String queryStr = queryStrBldr.toString(); //for debugging
275 Query q = em.createQuery(queryStr);
277 for (DocumentFilter.ParamBinding p : params) {
278 q.setParameter(p.getName(), p.getValue());
280 if (docFilter.getOffset() > 0) {
281 q.setFirstResult(docFilter.getOffset());
283 if (docFilter.getPageSize() > 0) {
284 q.setMaxResults(docFilter.getPageSize());
287 //FIXME is transaction required for get?
288 em.getTransaction().begin();
289 List list = q.getResultList();
290 em.getTransaction().commit();
291 DocumentWrapper<List> wrapDoc = new DocumentWrapperImpl<List>(list);
292 handler.handle(Action.GET_ALL, wrapDoc);
293 handler.complete(Action.GET_ALL, wrapDoc);
294 } catch (DocumentException de) {
296 } catch (Exception e) {
297 if (logger.isDebugEnabled()) {
298 logger.debug("Caught exception ", e);
300 throw new DocumentException(e);
303 releaseEntityManagerFactory(emf);
309 * @see org.collectionspace.services.common.storage.StorageClient#update(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
312 public void update(ServiceContext ctx, String id, DocumentHandler handler)
313 throws BadRequestException, DocumentNotFoundException,
316 throw new IllegalArgumentException(
317 "JpaStorageClient.update: ctx is missing");
319 if (handler == null) {
320 throw new IllegalArgumentException(
321 "JpaStorageClient.update: handler is missing");
323 EntityManagerFactory emf = null;
324 EntityManager em = null;
326 handler.prepare(Action.UPDATE);
327 Object entity = handler.getCommonPart();
329 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entity);
330 handler.handle(Action.UPDATE, wrapDoc);
331 emf = getEntityManagerFactory();
332 em = emf.createEntityManager();
333 em.getTransaction().begin();
334 Object entityFound = em.find(entity.getClass(), id);
335 if (entityFound == null) {
336 if (em != null && em.getTransaction().isActive()) {
337 em.getTransaction().rollback();
339 String msg = "could not find entity with id=" + id;
341 throw new DocumentNotFoundException(msg);
343 entity = em.merge(entity);
344 setValue(entity, "setUpdatedAtItem", Date.class, new Date());
345 if (logger.isDebugEnabled()) {
346 logger.debug("merged entity=" + entity.toString());
348 em.getTransaction().commit();
349 handler.complete(Action.UPDATE, wrapDoc);
350 } catch (BadRequestException bre) {
351 if (em != null && em.getTransaction().isActive()) {
352 em.getTransaction().rollback();
355 } catch (DocumentException de) {
357 } catch (Exception e) {
358 if (logger.isDebugEnabled()) {
359 logger.debug("Caught exception ", e);
361 throw new DocumentException(e);
364 releaseEntityManagerFactory(emf);
369 /* delete use delete to remove parent entity along with child entities
370 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
373 public void delete(ServiceContext ctx, String id)
374 throws DocumentNotFoundException,
378 throw new IllegalArgumentException(
379 "JpaStorageClient.delete: ctx is missing");
382 if (logger.isDebugEnabled()) {
383 logger.debug("deleting entity with id=" + id);
385 EntityManagerFactory emf = null;
386 EntityManager em = null;
389 //TODO: add tenant id
391 emf = getEntityManagerFactory();
392 em = emf.createEntityManager();
394 em.getTransaction().begin();
395 Object entityFound = getEntity(em, id);
396 if (entityFound == null) {
397 if (em != null && em.getTransaction().isActive()) {
398 em.getTransaction().rollback();
400 String msg = "could not find entity with id=" + id;
402 throw new DocumentNotFoundException(msg);
404 em.remove(entityFound);
405 em.getTransaction().commit();
407 } catch (DocumentException de) {
409 } catch (Exception e) {
410 if (logger.isDebugEnabled()) {
411 logger.debug("Caught exception ", e);
413 if (em != null && em.getTransaction().isActive()) {
414 em.getTransaction().rollback();
416 throw new DocumentException(e);
419 releaseEntityManagerFactory(emf);
425 * deleteWhere uses the where clause to delete an entity represented by the id
426 * it does not delete any child entities.
429 * @throws DocumentNotFoundException
430 * @throws DocumentException
432 public void deleteWhere(ServiceContext ctx, String id)
433 throws DocumentNotFoundException,
437 throw new IllegalArgumentException(
438 "JpaStorageClient.deleteWhere: ctx is missing");
441 if (logger.isDebugEnabled()) {
442 logger.debug("deleting entity with id=" + id);
444 EntityManagerFactory emf = null;
445 EntityManager em = null;
447 StringBuilder deleteStr = new StringBuilder("DELETE FROM ");
448 deleteStr.append(getEntityName(ctx));
449 deleteStr.append(" WHERE csid = :csid");
450 //TODO: add tenant id
452 emf = getEntityManagerFactory();
453 em = emf.createEntityManager();
454 Query q = em.createQuery(deleteStr.toString());
455 q.setParameter("csid", id);
458 em.getTransaction().begin();
459 rcount = q.executeUpdate();
461 if (em != null && em.getTransaction().isActive()) {
462 em.getTransaction().rollback();
464 String msg = "could not find entity with id=" + id;
466 throw new DocumentNotFoundException(msg);
468 em.getTransaction().commit();
470 } catch (DocumentException de) {
472 } catch (Exception e) {
473 if (logger.isDebugEnabled()) {
474 logger.debug("Caught exception ", e);
476 if (em != null && em.getTransaction().isActive()) {
477 em.getTransaction().rollback();
479 throw new DocumentException(e);
482 releaseEntityManagerFactory(emf);
488 * Gets the entity manager factory.
490 * @return the entity manager factory
492 public EntityManagerFactory getEntityManagerFactory() {
493 return getEntityManagerFactory(CS_PERSISTENCE_UNIT);
497 * Gets the entity manager factory.
499 * @param persistenceUnit the persistence unit
501 * @return the entity manager factory
503 public EntityManagerFactory getEntityManagerFactory(
504 String persistenceUnit) {
505 return Persistence.createEntityManagerFactory(persistenceUnit);
510 * Release entity manager factory.
514 public void releaseEntityManagerFactory(EntityManagerFactory emf) {
522 * getValue gets invokes specified accessor method on given object. Assumption
523 * is that this is used for JavaBean pattern getXXX methods only.
524 * @param o object to return value from
525 * @param methodName of method to invoke
526 * @return value returned of invocation
527 * @throws NoSuchMethodException
528 * @throws IllegalAccessException
529 * @throws InvocationTargetException
531 protected Object getValue(Object o, String methodName)
532 throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
533 if (methodName == null) {
534 String msg = methodName + " cannot be null";
536 throw new IllegalArgumentException(msg);
538 Class c = o.getClass();
539 Method m = c.getMethod(methodName);
541 Object r = m.invoke(o);
542 if (logger.isDebugEnabled()) {
543 logger.debug("getValue returned value=" + r
544 + " for " + c.getName());
550 * setValue mutates the given object by invoking specified method. Assumption
551 * is that this is used for JavaBean pattern setXXX methods only.
552 * @param o object to mutate
553 * @param methodName indicates method to invoke
554 * @param argType type of the only argument (assumed) to method
555 * @param argValue value of the only argument (assumed) to method
557 * @throws NoSuchMethodException
558 * @throws IllegalAccessException
559 * @throws InvocationTargetException
561 protected Object setValue(Object o, String methodName, Class argType, Object argValue)
562 throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
563 if (methodName == null) {
564 String msg = methodName + " cannot be null";
566 throw new IllegalArgumentException(msg);
568 if (argType == null) {
569 String msg = "argType cannot be null";
571 throw new IllegalArgumentException(msg);
573 Class c = o.getClass();
574 Method m = c.getMethod(methodName, argType);
575 Object r = m.invoke(o, argValue);
576 if (logger.isDebugEnabled()) {
577 logger.debug("completed invocation of " + methodName
578 + " for " + c.getName());
587 * @param csid the csid
589 * @throws Exception the exception
591 protected void setCsid(Object o, String csid) throws Exception {
593 String id = (String) getValue(o, "getCsid");
595 if (!id.equals(csid)) {
596 String msg = "Csids do not match!";
598 throw new BadRequestException(msg);
606 setValue(o, "setCsid", java.lang.String.class, csid);
610 * Gets the entity name.
614 * @return the entity name
616 protected String getEntityName(ServiceContext ctx) {
617 Object o = ctx.getProperty("entity-name");
619 throw new IllegalArgumentException("property entity-name missing in context "
627 * getEntity returns persistent entity for given id. it assumes that
628 * JpaStorageClientImpl is implemented using the JpaStorageClientImpl(entityClazz)
633 * @throws DocumentNotFoundException
634 * @throws UnsupportedOperationException if JpaStorageClientImpl is not implemented
635 * using the JpaStorageClientImpl(entityClazz)
638 protected Object getEntity(EntityManager em, String id) throws DocumentNotFoundException {
639 if (entityClazz == null) {
640 String msg = "Not constructed with JpaStorageClientImpl(entityClazz) ctor";
642 throw new UnsupportedOperationException(msg);
644 Object entityFound = em.find(entityClazz, id);
645 if (entityFound == null) {
646 if (em != null && em.getTransaction().isActive()) {
647 em.getTransaction().rollback();
649 String msg = "could not find entity of type=" + entityClazz.getName()
652 throw new DocumentNotFoundException(msg);
658 public void get(ServiceContext ctx, DocumentHandler handler)
659 throws DocumentNotFoundException, DocumentException {
660 throw new UnsupportedOperationException();