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)
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 "JpaStorageClient.get: ctx is missing");
170 if (handler == null) {
171 throw new IllegalArgumentException(
172 "JpaStorageClient.get: handler is missing");
174 DocumentFilter docFilter = handler.getDocumentFilter();
175 if (docFilter == null) {
176 docFilter = handler.createDocumentFilter(ctx);
178 EntityManagerFactory emf = null;
179 EntityManager em = null;
181 handler.prepare(Action.GET);
182 StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
183 queryStrBldr.append(getEntityName(ctx));
184 queryStrBldr.append(" a");
185 queryStrBldr.append(" WHERE csid = :csid");
186 //TODO: add tenant id
187 String where = docFilter.getWhereClause();
188 if ((null != where) && (where.length() > 0)) {
189 queryStrBldr.append(" AND " + where);
191 emf = getEntityManagerFactory();
192 em = emf.createEntityManager();
193 String queryStr = queryStrBldr.toString(); //for debugging
194 Query q = em.createQuery(queryStr);
195 q.setParameter("csid", id);
196 //TODO: add tenant id
199 if ((docFilter.getOffset() > 0) || (docFilter.getPageSize() > 0)) {
205 //require transaction for get?
206 em.getTransaction().begin();
207 o = q.getSingleResult();
208 em.getTransaction().commit();
209 } catch (NoResultException nre) {
210 if (em != null && em.getTransaction().isActive()) {
211 em.getTransaction().rollback();
213 String msg = "could not find entity with id=" + id;
214 logger.error(msg, nre);
215 throw new DocumentNotFoundException(msg, nre);
217 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(o);
218 handler.handle(Action.GET, wrapDoc);
219 handler.complete(Action.GET, wrapDoc);
220 } catch (DocumentException de) {
222 } catch (Exception e) {
223 if (logger.isDebugEnabled()) {
224 logger.debug("Caught exception ", e);
226 throw new DocumentException(e);
229 releaseEntityManagerFactory(emf);
235 * @see org.collectionspace.services.common.storage.StorageClient#getAll(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
238 public void getAll(ServiceContext ctx, DocumentHandler handler)
239 throws DocumentNotFoundException, DocumentException {
240 throw new UnsupportedOperationException("use getFiltered instead");
244 * @see org.collectionspace.services.common.storage.StorageClient#getFiltered(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
247 public void getFiltered(ServiceContext ctx, DocumentHandler handler)
248 throws DocumentNotFoundException, DocumentException {
250 throw new IllegalArgumentException(
251 "JpaStorageClient.getFiltered: ctx is missing");
253 if (handler == null) {
254 throw new IllegalArgumentException(
255 "JpaStorageClient.getFiltered: handler is missing");
257 DocumentFilter docFilter = handler.getDocumentFilter();
258 if (docFilter == null) {
259 docFilter = handler.createDocumentFilter(ctx);
261 EntityManagerFactory emf = null;
262 EntityManager em = null;
264 handler.prepare(Action.GET_ALL);
266 StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
267 queryStrBldr.append(getEntityName(ctx));
268 queryStrBldr.append(" a");
269 List<DocumentFilter.ParamBinding> params = docFilter.buildWhereForSearch(queryStrBldr);
270 //TODO: add tenant id
271 emf = getEntityManagerFactory();
272 em = emf.createEntityManager();
273 String queryStr = queryStrBldr.toString(); //for debugging
274 Query q = em.createQuery(queryStr);
276 for (DocumentFilter.ParamBinding p : params) {
277 q.setParameter(p.getName(), p.getValue());
279 if (docFilter.getOffset() > 0) {
280 q.setFirstResult(docFilter.getOffset());
282 if (docFilter.getPageSize() > 0) {
283 q.setMaxResults(docFilter.getPageSize());
286 //FIXME is transaction required for get?
287 em.getTransaction().begin();
288 List list = q.getResultList();
289 em.getTransaction().commit();
290 DocumentWrapper<List> wrapDoc = new DocumentWrapperImpl<List>(list);
291 handler.handle(Action.GET_ALL, wrapDoc);
292 handler.complete(Action.GET_ALL, wrapDoc);
293 } catch (DocumentException de) {
295 } catch (Exception e) {
296 if (logger.isDebugEnabled()) {
297 logger.debug("Caught exception ", e);
299 throw new DocumentException(e);
302 releaseEntityManagerFactory(emf);
308 * @see org.collectionspace.services.common.storage.StorageClient#update(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
311 public void update(ServiceContext ctx, String id, DocumentHandler handler)
312 throws BadRequestException, DocumentNotFoundException,
315 throw new IllegalArgumentException(
316 "JpaStorageClient.update: ctx is missing");
318 if (handler == null) {
319 throw new IllegalArgumentException(
320 "JpaStorageClient.update: handler is missing");
322 EntityManagerFactory emf = null;
323 EntityManager em = null;
325 handler.prepare(Action.UPDATE);
326 Object entity = handler.getCommonPart();
328 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entity);
329 handler.handle(Action.UPDATE, wrapDoc);
330 emf = getEntityManagerFactory();
331 em = emf.createEntityManager();
332 em.getTransaction().begin();
333 Object entityFound = em.find(entity.getClass(), id);
334 if (entityFound == null) {
335 if (em != null && em.getTransaction().isActive()) {
336 em.getTransaction().rollback();
338 String msg = "could not find entity with id=" + id;
340 throw new DocumentNotFoundException(msg);
342 entity = em.merge(entity);
343 setValue(entity, "setUpdatedAtItem", Date.class, new Date());
344 if (logger.isDebugEnabled()) {
345 logger.debug("merged entity=" + entity.toString());
347 em.getTransaction().commit();
348 handler.complete(Action.UPDATE, wrapDoc);
349 } catch (BadRequestException bre) {
350 if (em != null && em.getTransaction().isActive()) {
351 em.getTransaction().rollback();
354 } catch (DocumentException de) {
356 } catch (Exception e) {
357 if (logger.isDebugEnabled()) {
358 logger.debug("Caught exception ", e);
360 throw new DocumentException(e);
363 releaseEntityManagerFactory(emf);
368 /* delete use delete to remove parent entity along with child entities
369 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
372 public void delete(ServiceContext ctx, String id)
373 throws DocumentNotFoundException,
377 throw new IllegalArgumentException(
378 "JpaStorageClient.delete: ctx is missing");
381 if (logger.isDebugEnabled()) {
382 logger.debug("deleting entity with id=" + id);
384 EntityManagerFactory emf = null;
385 EntityManager em = null;
388 //TODO: add tenant id
390 emf = getEntityManagerFactory();
391 em = emf.createEntityManager();
393 em.getTransaction().begin();
394 Object entityFound = getEntity(em, id);
395 if (entityFound == null) {
396 if (em != null && em.getTransaction().isActive()) {
397 em.getTransaction().rollback();
399 String msg = "could not find entity with id=" + id;
401 throw new DocumentNotFoundException(msg);
403 em.remove(entityFound);
404 em.getTransaction().commit();
406 } catch (DocumentException de) {
408 } catch (Exception e) {
409 if (logger.isDebugEnabled()) {
410 logger.debug("Caught exception ", e);
412 if (em != null && em.getTransaction().isActive()) {
413 em.getTransaction().rollback();
415 throw new DocumentException(e);
418 releaseEntityManagerFactory(emf);
424 * deleteWhere uses the where clause to delete an entity represented by the id
425 * it does not delete any child entities.
428 * @throws DocumentNotFoundException
429 * @throws DocumentException
431 public void deleteWhere(ServiceContext ctx, String id)
432 throws DocumentNotFoundException,
436 throw new IllegalArgumentException(
437 "JpaStorageClient.deleteWhere: ctx is missing");
440 if (logger.isDebugEnabled()) {
441 logger.debug("deleting entity with id=" + id);
443 EntityManagerFactory emf = null;
444 EntityManager em = null;
446 StringBuilder deleteStr = new StringBuilder("DELETE FROM ");
447 deleteStr.append(getEntityName(ctx));
448 deleteStr.append(" WHERE csid = :csid");
449 //TODO: add tenant id
451 emf = getEntityManagerFactory();
452 em = emf.createEntityManager();
453 Query q = em.createQuery(deleteStr.toString());
454 q.setParameter("csid", id);
457 em.getTransaction().begin();
458 rcount = q.executeUpdate();
460 if (em != null && em.getTransaction().isActive()) {
461 em.getTransaction().rollback();
463 String msg = "could not find entity with id=" + id;
465 throw new DocumentNotFoundException(msg);
467 em.getTransaction().commit();
469 } catch (DocumentException de) {
471 } catch (Exception e) {
472 if (logger.isDebugEnabled()) {
473 logger.debug("Caught exception ", e);
475 if (em != null && em.getTransaction().isActive()) {
476 em.getTransaction().rollback();
478 throw new DocumentException(e);
481 releaseEntityManagerFactory(emf);
487 * Gets the entity manager factory.
489 * @return the entity manager factory
491 public EntityManagerFactory getEntityManagerFactory() {
492 return getEntityManagerFactory(CS_PERSISTENCE_UNIT);
496 * Gets the entity manager factory.
498 * @param persistenceUnit the persistence unit
500 * @return the entity manager factory
502 public EntityManagerFactory getEntityManagerFactory(
503 String persistenceUnit) {
504 return Persistence.createEntityManagerFactory(persistenceUnit);
509 * Release entity manager factory.
513 public void releaseEntityManagerFactory(EntityManagerFactory emf) {
521 * getValue gets invokes specified accessor method on given object. Assumption
522 * is that this is used for JavaBean pattern getXXX methods only.
523 * @param o object to return value from
524 * @param methodName of method to invoke
525 * @return value returned of invocation
526 * @throws NoSuchMethodException
527 * @throws IllegalAccessException
528 * @throws InvocationTargetException
530 protected Object getValue(Object o, String methodName)
531 throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
532 if (methodName == null) {
533 String msg = methodName + " cannot be null";
535 throw new IllegalArgumentException(msg);
537 Class c = o.getClass();
538 Method m = c.getMethod(methodName);
540 Object r = m.invoke(o);
541 if (logger.isDebugEnabled()) {
542 logger.debug("getValue returned value=" + r
543 + " for " + c.getName());
549 * setValue mutates the given object by invoking specified method. Assumption
550 * is that this is used for JavaBean pattern setXXX methods only.
551 * @param o object to mutate
552 * @param methodName indicates method to invoke
553 * @param argType type of the only argument (assumed) to method
554 * @param argValue value of the only argument (assumed) to method
556 * @throws NoSuchMethodException
557 * @throws IllegalAccessException
558 * @throws InvocationTargetException
560 protected Object setValue(Object o, String methodName, Class argType, Object argValue)
561 throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
562 if (methodName == null) {
563 String msg = methodName + " cannot be null";
565 throw new IllegalArgumentException(msg);
567 if (argType == null) {
568 String msg = "argType cannot be null";
570 throw new IllegalArgumentException(msg);
572 Class c = o.getClass();
573 Method m = c.getMethod(methodName, argType);
574 Object r = m.invoke(o, argValue);
575 if (logger.isDebugEnabled()) {
576 logger.debug("completed invocation of " + methodName
577 + " for " + c.getName());
586 * @param csid the csid
588 * @throws Exception the exception
590 protected void setCsid(Object o, String csid) throws Exception {
592 String id = (String) getValue(o, "getCsid");
594 if (!id.equals(csid)) {
595 String msg = "Csids do not match!";
597 throw new BadRequestException(msg);
605 setValue(o, "setCsid", java.lang.String.class, csid);
609 * Gets the entity name.
613 * @return the entity name
615 protected String getEntityName(ServiceContext ctx) {
616 Object o = ctx.getProperty("entity-name");
618 throw new IllegalArgumentException("property entity-name missing in context "
626 * getEntity returns persistent entity for given id. it assumes that
627 * JpaStorageClientImpl is implemented using the JpaStorageClientImpl(entityClazz)
632 * @throws DocumentNotFoundException
633 * @throws UnsupportedOperationException if JpaStorageClientImpl is not implemented
634 * using the JpaStorageClientImpl(entityClazz)
637 protected Object getEntity(EntityManager em, String id) throws DocumentNotFoundException {
638 if (entityClazz == null) {
639 String msg = "Not constructed with JpaStorageClientImpl(entityClazz) ctor";
641 throw new UnsupportedOperationException(msg);
643 Object entityFound = em.find(entityClazz, id);
644 if (entityFound == null) {
645 if (em != null && em.getTransaction().isActive()) {
646 em.getTransaction().rollback();
648 String msg = "could not find entity of type=" + entityClazz.getName()
651 throw new DocumentNotFoundException(msg);
657 public void get(ServiceContext ctx, DocumentHandler handler)
658 throws DocumentNotFoundException, DocumentException {
659 throw new UnsupportedOperationException();