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";
88 * Instantiates a new jpa storage client.
90 public JpaStorageClientImpl() {
94 * @see org.collectionspace.services.common.storage.StorageClient#create(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
97 public String create(ServiceContext ctx,
98 DocumentHandler handler) throws BadRequestException,
101 String docType = ctx.getDocumentType();
102 if (docType == null) {
103 throw new DocumentNotFoundException(
104 "Unable to find DocumentType for service " + ctx.getServiceName());
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 String docType = ctx.getDocumentType();
171 if (docType == null) {
172 throw new DocumentNotFoundException(
173 "Unable to find DocumentType for service " + ctx.getServiceName());
175 EntityManagerFactory emf = null;
176 EntityManager em = null;
178 handler.prepare(Action.GET);
179 StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
180 queryStrBldr.append(getEntityName(ctx));
181 queryStrBldr.append(" a");
182 queryStrBldr.append(" WHERE csid = :csid");
183 //TODO: add tenant id
184 String where = docFilter.getWhereClause();
185 if ((null != where) && (where.length() > 0)) {
186 queryStrBldr.append(" AND " + where);
188 emf = getEntityManagerFactory();
189 em = emf.createEntityManager();
190 String queryStr = queryStrBldr.toString(); //for debugging
191 Query q = em.createQuery(queryStr);
192 q.setParameter("csid", id);
193 //TODO: add tenant id
196 if ((docFilter.getOffset() > 0) || (docFilter.getPageSize() > 0)) {
202 //require transaction for get?
203 em.getTransaction().begin();
204 o = q.getSingleResult();
205 em.getTransaction().commit();
206 } catch (NoResultException nre) {
207 if (em != null && em.getTransaction().isActive()) {
208 em.getTransaction().rollback();
210 String msg = "could not find entity with id=" + id;
211 logger.error(msg, nre);
212 throw new DocumentNotFoundException(msg, nre);
214 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(o);
215 handler.handle(Action.GET, wrapDoc);
216 handler.complete(Action.GET, wrapDoc);
217 } catch (DocumentException de) {
219 } catch (Exception e) {
220 if (logger.isDebugEnabled()) {
221 logger.debug("Caught exception ", e);
223 throw new DocumentException(e);
226 releaseEntityManagerFactory(emf);
232 * @see org.collectionspace.services.common.storage.StorageClient#getAll(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
235 public void getAll(ServiceContext ctx, DocumentHandler handler)
236 throws DocumentNotFoundException, DocumentException {
237 throw new UnsupportedOperationException("use getFiltered instead");
241 * @see org.collectionspace.services.common.storage.StorageClient#getFiltered(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
244 public void getFiltered(ServiceContext ctx, DocumentHandler handler)
245 throws DocumentNotFoundException, DocumentException {
246 if (handler == null) {
247 throw new IllegalArgumentException(
248 "JpaStorageClient.getFiltered: handler is missing");
250 DocumentFilter docFilter = handler.getDocumentFilter();
251 if (docFilter == null) {
252 docFilter = handler.createDocumentFilter(ctx);
254 String docType = ctx.getDocumentType();
255 if (docType == null) {
256 throw new DocumentNotFoundException(
257 "Unable to find DocumentType for service " + ctx.getServiceName());
260 EntityManagerFactory emf = null;
261 EntityManager em = null;
263 handler.prepare(Action.GET_ALL);
265 StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
266 queryStrBldr.append(getEntityName(ctx));
267 queryStrBldr.append(" a");
268 List<DocumentFilter.ParamBinding> params = docFilter.buildWhereForSearch(queryStrBldr);
269 //TODO: add tenant id
270 emf = getEntityManagerFactory();
271 em = emf.createEntityManager();
272 String queryStr = queryStrBldr.toString(); //for debugging
273 Query q = em.createQuery(queryStr);
275 for (DocumentFilter.ParamBinding p : params) {
276 q.setParameter(p.getName(), p.getValue());
278 if (docFilter.getOffset() > 0) {
279 q.setFirstResult(docFilter.getOffset());
281 if (docFilter.getPageSize() > 0) {
282 q.setMaxResults(docFilter.getPageSize());
285 //FIXME is transaction required for get?
286 em.getTransaction().begin();
287 List list = q.getResultList();
288 em.getTransaction().commit();
289 DocumentWrapper<List> wrapDoc = new DocumentWrapperImpl<List>(list);
290 handler.handle(Action.GET_ALL, wrapDoc);
291 handler.complete(Action.GET_ALL, wrapDoc);
292 } catch (DocumentException de) {
294 } catch (Exception e) {
295 if (logger.isDebugEnabled()) {
296 logger.debug("Caught exception ", e);
298 throw new DocumentException(e);
301 releaseEntityManagerFactory(emf);
307 * @see org.collectionspace.services.common.storage.StorageClient#update(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
310 public void update(ServiceContext ctx, String id, DocumentHandler handler)
311 throws BadRequestException, DocumentNotFoundException,
313 String docType = ctx.getDocumentType();
314 if (docType == null) {
315 throw new DocumentNotFoundException(
316 "Unable to find DocumentType for service " + ctx.getServiceName());
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 setValue(entity, "setUpdatedAtItem", Date.class, new Date());
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);
344 em.getTransaction().commit();
345 handler.complete(Action.UPDATE, wrapDoc);
346 } catch (BadRequestException bre) {
347 if (em != null && em.getTransaction().isActive()) {
348 em.getTransaction().rollback();
351 } catch (DocumentException de) {
353 } catch (Exception e) {
354 if (logger.isDebugEnabled()) {
355 logger.debug("Caught exception ", e);
357 throw new DocumentException(e);
360 releaseEntityManagerFactory(emf);
366 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
369 public void delete(ServiceContext ctx, String id)
370 throws DocumentNotFoundException,
373 if (logger.isDebugEnabled()) {
374 logger.debug("deleting entity with id=" + id);
376 String docType = ctx.getDocumentType();
377 if (docType == null) {
378 throw new DocumentNotFoundException(
379 "Unable to find DocumentType for service " + ctx.getServiceName());
381 EntityManagerFactory emf = null;
382 EntityManager em = null;
384 StringBuilder deleteStr = new StringBuilder("DELETE FROM ");
385 deleteStr.append(getEntityName(ctx));
386 deleteStr.append(" WHERE csid = :csid");
387 //TODO: add tenant id
389 emf = getEntityManagerFactory();
390 em = emf.createEntityManager();
391 Query q = em.createQuery(deleteStr.toString());
392 q.setParameter("csid", id);
393 //TODO: add tenant id
395 em.getTransaction().begin();
396 rcount = q.executeUpdate();
398 if (em != null && em.getTransaction().isActive()) {
399 em.getTransaction().rollback();
401 String msg = "could not find entity with id=" + id;
403 throw new DocumentNotFoundException(msg);
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 * Gets the entity manager factory.
427 * @return the entity manager factory
429 public EntityManagerFactory getEntityManagerFactory() {
430 return getEntityManagerFactory(CS_PERSISTENCE_UNIT);
434 * Gets the entity manager factory.
436 * @param persistenceUnit the persistence unit
438 * @return the entity manager factory
440 public EntityManagerFactory getEntityManagerFactory(
441 String persistenceUnit) {
442 return Persistence.createEntityManagerFactory(persistenceUnit);
447 * Release entity manager factory.
451 public void releaseEntityManagerFactory(EntityManagerFactory emf) {
459 * getValue gets invokes specified accessor method on given object. Assumption
460 * is that this is used for JavaBean pattern getXXX methods only.
461 * @param o object to return value from
462 * @param methodName of method to invoke
463 * @return value returned of invocation
464 * @throws NoSuchMethodException
465 * @throws IllegalAccessException
466 * @throws InvocationTargetException
468 protected Object getValue(Object o, String methodName)
469 throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
470 if (methodName == null) {
471 String msg = methodName + " cannot be null";
473 throw new IllegalArgumentException(msg);
475 Class c = o.getClass();
476 Method m = c.getMethod(methodName);
478 Object r = m.invoke(o);
479 if (logger.isDebugEnabled()) {
480 logger.debug("getValue returned value=" + r
481 + " for " + c.getName());
487 * setValue mutates the given object by invoking specified method. Assumption
488 * is that this is used for JavaBean pattern setXXX methods only.
489 * @param o object to mutate
490 * @param methodName indicates method to invoke
491 * @param argType type of the only argument (assumed) to method
492 * @param argValue value of the only argument (assumed) to method
494 * @throws NoSuchMethodException
495 * @throws IllegalAccessException
496 * @throws InvocationTargetException
498 protected Object setValue(Object o, String methodName, Class argType, Object argValue)
499 throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
500 if (methodName == null) {
501 String msg = methodName + " cannot be null";
503 throw new IllegalArgumentException(msg);
505 if (argType == null) {
506 String msg = "argType cannot be null";
508 throw new IllegalArgumentException(msg);
510 Class c = o.getClass();
511 Method m = c.getMethod(methodName, argType);
512 Object r = m.invoke(o, argValue);
513 if (logger.isDebugEnabled()) {
514 logger.debug("completed invocation of " + methodName
515 + " for " + c.getName());
524 * @param csid the csid
526 * @throws Exception the exception
528 protected void setCsid(Object o, String csid) throws Exception {
530 String id = (String) getValue(o, "getCsid");
532 if (!id.equals(csid)) {
533 String msg = "Csids do not match!";
535 throw new BadRequestException(msg);
543 setValue(o, "setCsid", java.lang.String.class, csid);
547 * Gets the entity name.
551 * @return the entity name
553 protected String getEntityName(ServiceContext ctx) {
554 Object o = ctx.getProperty("entity-name");
556 throw new IllegalArgumentException("property entity-name missing in context "
564 public void get(ServiceContext ctx, DocumentHandler handler)
565 throws DocumentNotFoundException, DocumentException {
566 throw new UnsupportedOperationException();