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 private Class entityClazz;
87 * Instantiates a new jpa storage client.
89 public JpaStorageClientImpl() {
92 public JpaStorageClientImpl(Class entityClazz) {
93 this.entityClazz = entityClazz;
97 * @see org.collectionspace.services.common.storage.StorageClient#create(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
100 public String create(ServiceContext ctx,
101 DocumentHandler handler) throws BadRequestException,
105 throw new IllegalArgumentException(
106 "JpaStorageClient.create: ctx is missing");
108 if (handler == null) {
109 throw new IllegalArgumentException(
110 "JpaStorageClient.create: handler is missing");
112 EntityManagerFactory emf = null;
113 EntityManager em = null;
115 handler.prepare(Action.CREATE);
116 Object entity = handler.getCommonPart();
117 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entity);
118 handler.handle(Action.CREATE, wrapDoc);
119 setValue(entity, "setCreatedAtItem", Date.class, new Date());
120 emf = JpaStorageUtils.getEntityManagerFactory();
121 em = emf.createEntityManager();
122 em.getTransaction().begin();
124 em.getTransaction().commit();
125 handler.complete(Action.CREATE, wrapDoc);
126 return (String) getValue(entity, "getCsid");
127 } catch (BadRequestException bre) {
128 if (em != null && em.getTransaction().isActive()) {
129 em.getTransaction().rollback();
132 } catch (DocumentException de) {
134 } catch (Exception e) {
135 if (em != null && em.getTransaction().isActive()) {
136 em.getTransaction().rollback();
138 if (logger.isDebugEnabled()) {
139 logger.debug("Caught exception ", e);
141 throw new DocumentException(e);
144 JpaStorageUtils.releaseEntityManagerFactory(emf);
151 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.util.List, org.collectionspace.services.common.document.DocumentHandler)
154 public void get(ServiceContext ctx, List<String> csidList, DocumentHandler handler)
155 throws DocumentNotFoundException, DocumentException {
156 throw new UnsupportedOperationException();
160 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
163 public void get(ServiceContext ctx, String id, DocumentHandler handler)
164 throws DocumentNotFoundException, DocumentException {
166 throw new IllegalArgumentException(
167 "JpaStorageClient.get: ctx is missing");
169 if (handler == null) {
170 throw new IllegalArgumentException(
171 "JpaStorageClient.get: handler is missing");
173 DocumentFilter docFilter = handler.getDocumentFilter();
174 if (docFilter == null) {
175 docFilter = handler.createDocumentFilter();
177 EntityManagerFactory emf = null;
178 EntityManager em = null;
180 handler.prepare(Action.GET);
183 o = JpaStorageUtils.getEntity(getEntityName(ctx), id, docFilter);
184 } catch (NoResultException nre) {
185 if (em != null && em.getTransaction().isActive()) {
186 em.getTransaction().rollback();
188 String msg = "could not find entity with id=" + id;
189 logger.error(msg, nre);
190 throw new DocumentNotFoundException(msg, nre);
192 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(o);
193 handler.handle(Action.GET, wrapDoc);
194 handler.complete(Action.GET, wrapDoc);
195 } catch (DocumentException de) {
197 } catch (Exception e) {
198 if (logger.isDebugEnabled()) {
199 logger.debug("Caught exception ", e);
201 throw new DocumentException(e);
204 JpaStorageUtils.releaseEntityManagerFactory(emf);
210 * @see org.collectionspace.services.common.storage.StorageClient#getAll(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
213 public void getAll(ServiceContext ctx, DocumentHandler handler)
214 throws DocumentNotFoundException, DocumentException {
215 throw new UnsupportedOperationException("use getFiltered instead");
219 * @see org.collectionspace.services.common.storage.StorageClient#getFiltered(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
222 public void getFiltered(ServiceContext ctx, DocumentHandler handler)
223 throws DocumentNotFoundException, DocumentException {
225 throw new IllegalArgumentException(
226 "JpaStorageClient.getFiltered: ctx is missing");
228 if (handler == null) {
229 throw new IllegalArgumentException(
230 "JpaStorageClient.getFiltered: handler is missing");
233 DocumentFilter docFilter = handler.getDocumentFilter();
234 if (docFilter == null) {
235 docFilter = handler.createDocumentFilter();
237 EntityManagerFactory emf = null;
238 EntityManager em = null;
240 handler.prepare(Action.GET_ALL);
242 StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
243 queryStrBldr.append(getEntityName(ctx));
244 queryStrBldr.append(" a");
245 List<DocumentFilter.ParamBinding> params = docFilter.buildWhereForSearch(queryStrBldr);
246 //TODO: add tenant id
247 emf = JpaStorageUtils.getEntityManagerFactory();
248 em = emf.createEntityManager();
249 String queryStr = queryStrBldr.toString(); //for debugging
250 Query q = em.createQuery(queryStr);
252 for (DocumentFilter.ParamBinding p : params) {
253 q.setParameter(p.getName(), p.getValue());
255 if (docFilter.getOffset() > 0) {
256 q.setFirstResult(docFilter.getOffset());
258 if (docFilter.getPageSize() > 0) {
259 q.setMaxResults(docFilter.getPageSize());
262 //FIXME is transaction required for get?
263 em.getTransaction().begin();
264 List list = q.getResultList();
265 em.getTransaction().commit();
266 DocumentWrapper<List> wrapDoc = new DocumentWrapperImpl<List>(list);
267 handler.handle(Action.GET_ALL, wrapDoc);
268 handler.complete(Action.GET_ALL, wrapDoc);
269 } catch (DocumentException de) {
271 } catch (Exception e) {
272 if (logger.isDebugEnabled()) {
273 logger.debug("Caught exception ", e);
275 throw new DocumentException(e);
278 JpaStorageUtils.releaseEntityManagerFactory(emf);
284 * @see org.collectionspace.services.common.storage.StorageClient#update(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
287 public void update(ServiceContext ctx, String id, DocumentHandler handler)
288 throws BadRequestException, DocumentNotFoundException,
291 throw new IllegalArgumentException(
292 "JpaStorageClient.update: ctx is missing");
294 if (handler == null) {
295 throw new IllegalArgumentException(
296 "JpaStorageClient.update: handler is missing");
298 EntityManagerFactory emf = null;
299 EntityManager em = null;
301 handler.prepare(Action.UPDATE);
302 Object entity = handler.getCommonPart();
304 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entity);
305 handler.handle(Action.UPDATE, wrapDoc);
306 emf = JpaStorageUtils.getEntityManagerFactory();
307 em = emf.createEntityManager();
308 em.getTransaction().begin();
309 Object entityFound = em.find(entity.getClass(), id);
310 if (entityFound == null) {
311 if (em != null && em.getTransaction().isActive()) {
312 em.getTransaction().rollback();
314 String msg = "could not find entity with id=" + id;
316 throw new DocumentNotFoundException(msg);
318 entity = em.merge(entity);
319 setValue(entity, "setUpdatedAtItem", Date.class, new Date());
320 if (logger.isDebugEnabled()) {
321 logger.debug("merged entity=" + entity.toString());
323 em.getTransaction().commit();
324 handler.complete(Action.UPDATE, wrapDoc);
325 } catch (BadRequestException bre) {
326 if (em != null && em.getTransaction().isActive()) {
327 em.getTransaction().rollback();
330 } catch (DocumentException de) {
332 } catch (Exception e) {
333 if (logger.isDebugEnabled()) {
334 logger.debug("Caught exception ", e);
336 throw new DocumentException(e);
339 JpaStorageUtils.releaseEntityManagerFactory(emf);
344 /* delete use delete to remove parent entity along with child entities
345 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
348 public void delete(ServiceContext ctx, String id)
349 throws DocumentNotFoundException,
353 throw new IllegalArgumentException(
354 "JpaStorageClient.delete: ctx is missing");
357 if (logger.isDebugEnabled()) {
358 logger.debug("deleting entity with id=" + id);
360 EntityManagerFactory emf = null;
361 EntityManager em = null;
364 //TODO: add tenant id
366 emf = JpaStorageUtils.getEntityManagerFactory();
367 em = emf.createEntityManager();
369 em.getTransaction().begin();
370 Object entityFound = getEntity(em, id);
371 if (entityFound == null) {
372 if (em != null && em.getTransaction().isActive()) {
373 em.getTransaction().rollback();
375 String msg = "could not find entity with id=" + id;
377 throw new DocumentNotFoundException(msg);
379 em.remove(entityFound);
380 em.getTransaction().commit();
382 } catch (DocumentException de) {
384 } catch (Exception e) {
385 if (logger.isDebugEnabled()) {
386 logger.debug("Caught exception ", e);
388 if (em != null && em.getTransaction().isActive()) {
389 em.getTransaction().rollback();
391 throw new DocumentException(e);
394 JpaStorageUtils.releaseEntityManagerFactory(emf);
400 * deleteWhere uses the where clause to delete an entity represented by the id
401 * it does not delete any child entities.
404 * @throws DocumentNotFoundException
405 * @throws DocumentException
407 public void deleteWhere(ServiceContext ctx, String id)
408 throws DocumentNotFoundException,
412 throw new IllegalArgumentException(
413 "JpaStorageClient.deleteWhere: ctx is missing");
416 if (logger.isDebugEnabled()) {
417 logger.debug("deleting entity with id=" + id);
419 EntityManagerFactory emf = null;
420 EntityManager em = null;
422 StringBuilder deleteStr = new StringBuilder("DELETE FROM ");
423 deleteStr.append(getEntityName(ctx));
424 deleteStr.append(" WHERE csid = :csid");
425 //TODO: add tenant id
427 emf = JpaStorageUtils.getEntityManagerFactory();
428 em = emf.createEntityManager();
429 Query q = em.createQuery(deleteStr.toString());
430 q.setParameter("csid", id);
433 em.getTransaction().begin();
434 rcount = q.executeUpdate();
436 if (em != null && em.getTransaction().isActive()) {
437 em.getTransaction().rollback();
439 String msg = "could not find entity with id=" + id;
441 throw new DocumentNotFoundException(msg);
443 em.getTransaction().commit();
445 } catch (DocumentException de) {
447 } catch (Exception e) {
448 if (logger.isDebugEnabled()) {
449 logger.debug("Caught exception ", e);
451 if (em != null && em.getTransaction().isActive()) {
452 em.getTransaction().rollback();
454 throw new DocumentException(e);
457 JpaStorageUtils.releaseEntityManagerFactory(emf);
465 * getValue gets invokes specified accessor method on given object. Assumption
466 * is that this is used for JavaBean pattern getXXX methods only.
467 * @param o object to return value from
468 * @param methodName of method to invoke
469 * @return value returned of invocation
470 * @throws NoSuchMethodException
471 * @throws IllegalAccessException
472 * @throws InvocationTargetException
474 protected Object getValue(Object o, String methodName)
475 throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
476 if (methodName == null) {
477 String msg = methodName + " cannot be null";
479 throw new IllegalArgumentException(msg);
481 Class c = o.getClass();
482 Method m = c.getMethod(methodName);
484 Object r = m.invoke(o);
485 if (logger.isDebugEnabled()) {
486 logger.debug("getValue returned value=" + r
487 + " for " + c.getName());
493 * setValue mutates the given object by invoking specified method. Assumption
494 * is that this is used for JavaBean pattern setXXX methods only.
495 * @param o object to mutate
496 * @param methodName indicates method to invoke
497 * @param argType type of the only argument (assumed) to method
498 * @param argValue value of the only argument (assumed) to method
500 * @throws NoSuchMethodException
501 * @throws IllegalAccessException
502 * @throws InvocationTargetException
504 protected Object setValue(Object o, String methodName, Class argType, Object argValue)
505 throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
506 if (methodName == null) {
507 String msg = methodName + " cannot be null";
509 throw new IllegalArgumentException(msg);
511 if (argType == null) {
512 String msg = "argType cannot be null";
514 throw new IllegalArgumentException(msg);
516 Class c = o.getClass();
517 Method m = c.getMethod(methodName, argType);
518 Object r = m.invoke(o, argValue);
519 if (logger.isDebugEnabled()) {
520 logger.debug("completed invocation of " + methodName
521 + " for " + c.getName());
530 * @param csid the csid
532 * @throws Exception the exception
534 protected void setCsid(Object o, String csid) throws Exception {
536 String id = (String) getValue(o, "getCsid");
538 if (!id.equals(csid)) {
539 String msg = "Csids do not match!";
541 throw new BadRequestException(msg);
549 setValue(o, "setCsid", java.lang.String.class, csid);
553 * Gets the entity name.
557 * @return the entity name
559 protected String getEntityName(ServiceContext ctx) {
560 Object o = ctx.getProperty("entity-name");
562 throw new IllegalArgumentException("property entity-name missing in context "
570 * getEntity returns persistent entity for given id. it assumes that
571 * JpaStorageClientImpl is implemented using the JpaStorageClientImpl(entityClazz)
576 * @throws DocumentNotFoundException
577 * @throws UnsupportedOperationException if JpaStorageClientImpl is not implemented
578 * using the JpaStorageClientImpl(entityClazz)
581 protected Object getEntity(EntityManager em, String id) throws DocumentNotFoundException {
582 if (entityClazz == null) {
583 String msg = "Not constructed with JpaStorageClientImpl(entityClazz) ctor";
585 throw new UnsupportedOperationException(msg);
587 Object entityFound = em.find(entityClazz, id);
588 if (entityFound == null) {
589 if (em != null && em.getTransaction().isActive()) {
590 em.getTransaction().rollback();
592 String msg = "could not find entity of type=" + entityClazz.getName()
595 throw new DocumentNotFoundException(msg);
601 public void get(ServiceContext ctx, DocumentHandler handler)
602 throws DocumentNotFoundException, DocumentException {
603 throw new UnsupportedOperationException();