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,
106 if (handler == null) {
107 throw new IllegalArgumentException(
108 "JpaStorageClient.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 setValue(entity, "setCreatedAtItem", Date.class, new Date());
118 emf = getEntityManagerFactory();
119 em = emf.createEntityManager();
120 em.getTransaction().begin();
122 em.getTransaction().commit();
123 handler.complete(Action.CREATE, wrapDoc);
124 return (String) getValue(entity, "getCsid");
125 } catch (BadRequestException bre) {
126 if (em != null && em.getTransaction().isActive()) {
127 em.getTransaction().rollback();
130 } catch (DocumentException de) {
132 } catch (Exception e) {
133 if (em != null && em.getTransaction().isActive()) {
134 em.getTransaction().rollback();
136 if (logger.isDebugEnabled()) {
137 logger.debug("Caught exception ", e);
139 throw new DocumentException(e);
142 releaseEntityManagerFactory(emf);
149 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.util.List, org.collectionspace.services.common.document.DocumentHandler)
151 public void get(ServiceContext ctx, List<String> csidList, DocumentHandler handler)
152 throws DocumentNotFoundException, DocumentException {
153 throw new UnsupportedOperationException();
157 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
160 public void get(ServiceContext ctx, String id, DocumentHandler handler)
161 throws DocumentNotFoundException, DocumentException {
162 if (handler == null) {
163 throw new IllegalArgumentException(
164 "JpaStorageClient.get: handler is missing");
166 DocumentFilter docFilter = handler.getDocumentFilter();
167 if (docFilter == null) {
168 docFilter = handler.createDocumentFilter(ctx);
170 EntityManagerFactory emf = null;
171 EntityManager em = null;
173 handler.prepare(Action.GET);
174 StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
175 queryStrBldr.append(getEntityName(ctx));
176 queryStrBldr.append(" a");
177 queryStrBldr.append(" WHERE csid = :csid");
178 //TODO: add tenant id
179 String where = docFilter.getWhereClause();
180 if ((null != where) && (where.length() > 0)) {
181 queryStrBldr.append(" AND " + where);
183 emf = getEntityManagerFactory();
184 em = emf.createEntityManager();
185 String queryStr = queryStrBldr.toString(); //for debugging
186 Query q = em.createQuery(queryStr);
187 q.setParameter("csid", id);
188 //TODO: add tenant id
191 if ((docFilter.getOffset() > 0) || (docFilter.getPageSize() > 0)) {
197 //require transaction for get?
198 em.getTransaction().begin();
199 o = q.getSingleResult();
200 em.getTransaction().commit();
201 } catch (NoResultException nre) {
202 if (em != null && em.getTransaction().isActive()) {
203 em.getTransaction().rollback();
205 String msg = "could not find entity with id=" + id;
206 logger.error(msg, nre);
207 throw new DocumentNotFoundException(msg, nre);
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 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 if (handler == null) {
242 throw new IllegalArgumentException(
243 "JpaStorageClient.getFiltered: handler is missing");
245 DocumentFilter docFilter = handler.getDocumentFilter();
246 if (docFilter == null) {
247 docFilter = handler.createDocumentFilter(ctx);
249 EntityManagerFactory emf = null;
250 EntityManager em = null;
252 handler.prepare(Action.GET_ALL);
254 StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
255 queryStrBldr.append(getEntityName(ctx));
256 queryStrBldr.append(" a");
257 List<DocumentFilter.ParamBinding> params = docFilter.buildWhereForSearch(queryStrBldr);
258 //TODO: add tenant id
259 emf = getEntityManagerFactory();
260 em = emf.createEntityManager();
261 String queryStr = queryStrBldr.toString(); //for debugging
262 Query q = em.createQuery(queryStr);
264 for (DocumentFilter.ParamBinding p : params) {
265 q.setParameter(p.getName(), p.getValue());
267 if (docFilter.getOffset() > 0) {
268 q.setFirstResult(docFilter.getOffset());
270 if (docFilter.getPageSize() > 0) {
271 q.setMaxResults(docFilter.getPageSize());
274 //FIXME is transaction required for get?
275 em.getTransaction().begin();
276 List list = q.getResultList();
277 em.getTransaction().commit();
278 DocumentWrapper<List> wrapDoc = new DocumentWrapperImpl<List>(list);
279 handler.handle(Action.GET_ALL, wrapDoc);
280 handler.complete(Action.GET_ALL, wrapDoc);
281 } catch (DocumentException de) {
283 } catch (Exception e) {
284 if (logger.isDebugEnabled()) {
285 logger.debug("Caught exception ", e);
287 throw new DocumentException(e);
290 releaseEntityManagerFactory(emf);
296 * @see org.collectionspace.services.common.storage.StorageClient#update(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
299 public void update(ServiceContext ctx, String id, DocumentHandler handler)
300 throws BadRequestException, DocumentNotFoundException,
302 if (handler == null) {
303 throw new IllegalArgumentException(
304 "JpaStorageClient.update: handler is missing");
306 EntityManagerFactory emf = null;
307 EntityManager em = null;
309 handler.prepare(Action.UPDATE);
310 Object entity = handler.getCommonPart();
312 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entity);
313 handler.handle(Action.UPDATE, wrapDoc);
314 emf = getEntityManagerFactory();
315 em = emf.createEntityManager();
316 em.getTransaction().begin();
317 Object entityFound = em.find(entity.getClass(), id);
318 if (entityFound == null) {
319 if (em != null && em.getTransaction().isActive()) {
320 em.getTransaction().rollback();
322 String msg = "could not find entity with id=" + id;
324 throw new DocumentNotFoundException(msg);
326 entity = em.merge(entity);
327 setValue(entity, "setUpdatedAtItem", Date.class, new Date());
328 if (logger.isDebugEnabled()) {
329 logger.debug("merged entity=" + entity.toString());
331 em.getTransaction().commit();
332 handler.complete(Action.UPDATE, wrapDoc);
333 } catch (BadRequestException bre) {
334 if (em != null && em.getTransaction().isActive()) {
335 em.getTransaction().rollback();
338 } catch (DocumentException de) {
340 } catch (Exception e) {
341 if (logger.isDebugEnabled()) {
342 logger.debug("Caught exception ", e);
344 throw new DocumentException(e);
347 releaseEntityManagerFactory(emf);
352 /* delete use delete to remove parent entity along with child entities
353 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
356 public void delete(ServiceContext ctx, String id)
357 throws DocumentNotFoundException,
360 if (logger.isDebugEnabled()) {
361 logger.debug("deleting entity with id=" + id);
363 EntityManagerFactory emf = null;
364 EntityManager em = null;
367 //TODO: add tenant id
369 emf = getEntityManagerFactory();
370 em = emf.createEntityManager();
372 em.getTransaction().begin();
373 Object entityFound = getEntity(em, id);
374 if (entityFound == null) {
375 if (em != null && em.getTransaction().isActive()) {
376 em.getTransaction().rollback();
378 String msg = "could not find entity with id=" + id;
380 throw new DocumentNotFoundException(msg);
382 em.remove(entityFound);
383 em.getTransaction().commit();
385 } catch (DocumentException de) {
387 } catch (Exception e) {
388 if (logger.isDebugEnabled()) {
389 logger.debug("Caught exception ", e);
391 if (em != null && em.getTransaction().isActive()) {
392 em.getTransaction().rollback();
394 throw new DocumentException(e);
397 releaseEntityManagerFactory(emf);
403 * deleteWhere uses the where clause to delete an entity represented by the id
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 if (logger.isDebugEnabled()) {
416 logger.debug("deleting entity with id=" + id);
418 EntityManagerFactory emf = null;
419 EntityManager em = null;
421 StringBuilder deleteStr = new StringBuilder("DELETE FROM ");
422 deleteStr.append(getEntityName(ctx));
423 deleteStr.append(" WHERE csid = :csid");
424 //TODO: add tenant id
426 emf = getEntityManagerFactory();
427 em = emf.createEntityManager();
428 Query q = em.createQuery(deleteStr.toString());
429 q.setParameter("csid", id);
430 //TODO: add tenant id
432 em.getTransaction().begin();
433 rcount = q.executeUpdate();
435 if (em != null && em.getTransaction().isActive()) {
436 em.getTransaction().rollback();
438 String msg = "could not find entity with id=" + id;
440 throw new DocumentNotFoundException(msg);
442 em.getTransaction().commit();
444 } catch (DocumentException de) {
446 } catch (Exception e) {
447 if (logger.isDebugEnabled()) {
448 logger.debug("Caught exception ", e);
450 if (em != null && em.getTransaction().isActive()) {
451 em.getTransaction().rollback();
453 throw new DocumentException(e);
456 releaseEntityManagerFactory(emf);
462 * Gets the entity manager factory.
464 * @return the entity manager factory
466 public EntityManagerFactory getEntityManagerFactory() {
467 return getEntityManagerFactory(CS_PERSISTENCE_UNIT);
471 * Gets the entity manager factory.
473 * @param persistenceUnit the persistence unit
475 * @return the entity manager factory
477 public EntityManagerFactory getEntityManagerFactory(
478 String persistenceUnit) {
479 return Persistence.createEntityManagerFactory(persistenceUnit);
484 * Release entity manager factory.
488 public void releaseEntityManagerFactory(EntityManagerFactory emf) {
496 * getValue gets invokes specified accessor method on given object. Assumption
497 * is that this is used for JavaBean pattern getXXX methods only.
498 * @param o object to return value from
499 * @param methodName of method to invoke
500 * @return value returned of invocation
501 * @throws NoSuchMethodException
502 * @throws IllegalAccessException
503 * @throws InvocationTargetException
505 protected Object getValue(Object o, String methodName)
506 throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
507 if (methodName == null) {
508 String msg = methodName + " cannot be null";
510 throw new IllegalArgumentException(msg);
512 Class c = o.getClass();
513 Method m = c.getMethod(methodName);
515 Object r = m.invoke(o);
516 if (logger.isDebugEnabled()) {
517 logger.debug("getValue returned value=" + r
518 + " for " + c.getName());
524 * setValue mutates the given object by invoking specified method. Assumption
525 * is that this is used for JavaBean pattern setXXX methods only.
526 * @param o object to mutate
527 * @param methodName indicates method to invoke
528 * @param argType type of the only argument (assumed) to method
529 * @param argValue value of the only argument (assumed) to method
531 * @throws NoSuchMethodException
532 * @throws IllegalAccessException
533 * @throws InvocationTargetException
535 protected Object setValue(Object o, String methodName, Class argType, Object argValue)
536 throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
537 if (methodName == null) {
538 String msg = methodName + " cannot be null";
540 throw new IllegalArgumentException(msg);
542 if (argType == null) {
543 String msg = "argType cannot be null";
545 throw new IllegalArgumentException(msg);
547 Class c = o.getClass();
548 Method m = c.getMethod(methodName, argType);
549 Object r = m.invoke(o, argValue);
550 if (logger.isDebugEnabled()) {
551 logger.debug("completed invocation of " + methodName
552 + " for " + c.getName());
561 * @param csid the csid
563 * @throws Exception the exception
565 protected void setCsid(Object o, String csid) throws Exception {
567 String id = (String) getValue(o, "getCsid");
569 if (!id.equals(csid)) {
570 String msg = "Csids do not match!";
572 throw new BadRequestException(msg);
580 setValue(o, "setCsid", java.lang.String.class, csid);
584 * Gets the entity name.
588 * @return the entity name
590 protected String getEntityName(ServiceContext ctx) {
591 Object o = ctx.getProperty("entity-name");
593 throw new IllegalArgumentException("property entity-name missing in context "
601 * getEntity returns persistent entity for given id. it assumes that
602 * JpaStorageClientImpl is implemented using the JpaStorageClientImpl(entityClazz)
607 * @throws DocumentNotFoundException
608 * @throws UnsupportedOperationException if JpaStorageClientImpl is not implemented
609 * using the JpaStorageClientImpl(entityClazz)
612 protected Object getEntity(EntityManager em, String id) throws DocumentNotFoundException {
613 if (entityClazz == null) {
614 String msg = "Not constructed with JpaStorageClientImpl(entityClazz) ctor";
616 throw new UnsupportedOperationException(msg);
618 Object entityFound = em.find(entityClazz, id);
619 if (entityFound == null) {
620 if (em != null && em.getTransaction().isActive()) {
621 em.getTransaction().rollback();
623 String msg = "could not find entity of type=" + entityClazz.getName()
626 throw new DocumentNotFoundException(msg);
632 public void get(ServiceContext ctx, DocumentHandler handler)
633 throws DocumentNotFoundException, DocumentException {
634 throw new UnsupportedOperationException();