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.List;
23 import javax.persistence.EntityManager;
24 import javax.persistence.EntityManagerFactory;
25 import javax.persistence.NoResultException;
26 import javax.persistence.Persistence;
27 import javax.persistence.Query;
28 import org.collectionspace.services.common.context.ServiceContext;
29 import org.collectionspace.services.common.document.BadRequestException;
30 import org.collectionspace.services.common.document.DocumentException;
31 import org.collectionspace.services.common.document.DocumentFilter;
32 import org.collectionspace.services.common.document.DocumentHandler;
33 import org.collectionspace.services.common.document.DocumentNotFoundException;
34 import org.collectionspace.services.common.document.DocumentHandler.Action;
35 import org.collectionspace.services.common.document.DocumentWrapper;
36 import org.collectionspace.services.common.document.DocumentWrapperImpl;
37 import org.collectionspace.services.common.storage.StorageClient;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
42 * JpaStorageClient is used to perform CRUD operations on SQL storage using JPA.
43 * It uses @see DocumentHandler as IOHandler with the client.
44 * All the operations in this client are carried out under their own transactions.
45 * A call to any method would start and commit/rollback a transaction.
47 * $LastChangedRevision: $ $LastChangedDate: $
49 public class JpaStorageClientImpl implements StorageClient {
52 private final Logger logger = LoggerFactory.getLogger(JpaStorageClientImpl.class);
53 /** The Constant CS_PERSISTENCE_UNIT. */
54 public final static String CS_PERSISTENCE_UNIT = "org.collectionspace.services";
57 * Instantiates a new jpa storage client.
59 public JpaStorageClientImpl() {
63 * @see org.collectionspace.services.common.storage.StorageClient#create(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
66 public String create(ServiceContext ctx,
67 DocumentHandler handler) throws BadRequestException,
70 String docType = ctx.getDocumentType();
71 if (docType == null) {
72 throw new DocumentNotFoundException(
73 "Unable to find DocumentType for service " + ctx.getServiceName());
75 if (handler == null) {
76 throw new IllegalArgumentException(
77 "JpaStorageClient.create: handler is missing");
79 EntityManagerFactory emf = null;
80 EntityManager em = null;
82 handler.prepare(Action.CREATE);
83 Object entity = handler.getCommonPart();
84 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entity);
85 handler.handle(Action.CREATE, wrapDoc);
86 emf = getEntityManagerFactory();
87 em = emf.createEntityManager();
88 em.getTransaction().begin();
90 em.getTransaction().commit();
91 handler.complete(Action.CREATE, wrapDoc);
92 return (String) getValue(entity, "getCsid");
93 } catch (DocumentException de) {
95 } catch (Exception e) {
96 if (em != null && em.getTransaction().isActive()) {
97 em.getTransaction().rollback();
99 if (logger.isDebugEnabled()) {
100 logger.debug("Caught exception ", e);
102 throw new DocumentException(e);
105 releaseEntityManagerFactory(emf);
112 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.util.List, org.collectionspace.services.common.document.DocumentHandler)
114 public void get(ServiceContext ctx, List<String> csidList, DocumentHandler handler)
115 throws DocumentNotFoundException, DocumentException {
116 throw new UnsupportedOperationException();
120 * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
123 public void get(ServiceContext ctx, String id, DocumentHandler handler)
124 throws DocumentNotFoundException, DocumentException {
125 if (handler == null) {
126 throw new IllegalArgumentException(
127 "JpaStorageClient.get: handler is missing");
129 DocumentFilter docFilter = handler.getDocumentFilter();
130 if (docFilter == null) {
131 docFilter = handler.createDocumentFilter(ctx);
133 String docType = ctx.getDocumentType();
134 if (docType == null) {
135 throw new DocumentNotFoundException(
136 "Unable to find DocumentType for service " + ctx.getServiceName());
138 EntityManagerFactory emf = null;
139 EntityManager em = null;
141 handler.prepare(Action.GET);
142 StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
143 queryStrBldr.append(getEntityName(ctx));
144 queryStrBldr.append(" a");
145 queryStrBldr.append(" WHERE csid = :csid");
146 //TODO: add tenant id
147 String where = docFilter.getWhereClause();
148 if ((null != where) && (where.length() > 0)) {
149 queryStrBldr.append(" AND " + where);
151 emf = getEntityManagerFactory();
152 em = emf.createEntityManager();
153 String queryStr = queryStrBldr.toString(); //for debugging
154 Query q = em.createQuery(queryStr);
155 q.setParameter("csid", id);
156 //TODO: add tenant id
159 if ((docFilter.getOffset() > 0) || (docFilter.getPageSize() > 0)) {
165 //require transaction for get?
166 em.getTransaction().begin();
167 o = q.getSingleResult();
168 em.getTransaction().commit();
169 } catch (NoResultException nre) {
170 if (em != null && em.getTransaction().isActive()) {
171 em.getTransaction().rollback();
173 String msg = "could not find entity with id=" + id;
174 logger.error(msg, nre);
175 throw new DocumentNotFoundException(msg, nre);
177 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(o);
178 handler.handle(Action.GET, wrapDoc);
179 handler.complete(Action.GET, wrapDoc);
180 } catch (DocumentException de) {
182 } catch (Exception e) {
183 if (logger.isDebugEnabled()) {
184 logger.debug("Caught exception ", e);
186 throw new DocumentException(e);
189 releaseEntityManagerFactory(emf);
195 * @see org.collectionspace.services.common.storage.StorageClient#getAll(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
198 public void getAll(ServiceContext ctx, DocumentHandler handler)
199 throws DocumentNotFoundException, DocumentException {
200 throw new UnsupportedOperationException("use getFiltered instead");
204 * @see org.collectionspace.services.common.storage.StorageClient#getFiltered(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
207 public void getFiltered(ServiceContext ctx, DocumentHandler handler)
208 throws DocumentNotFoundException, DocumentException {
209 if (handler == null) {
210 throw new IllegalArgumentException(
211 "JpaStorageClient.getFiltered: handler is missing");
213 DocumentFilter docFilter = handler.getDocumentFilter();
214 if (docFilter == null) {
215 docFilter = handler.createDocumentFilter(ctx);
217 String docType = ctx.getDocumentType();
218 if (docType == null) {
219 throw new DocumentNotFoundException(
220 "Unable to find DocumentType for service " + ctx.getServiceName());
223 EntityManagerFactory emf = null;
224 EntityManager em = null;
226 handler.prepare(Action.GET_ALL);
228 StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
229 queryStrBldr.append(getEntityName(ctx));
230 queryStrBldr.append(" a");
231 List<DocumentFilter.ParamBinding> params = docFilter.buildWhereForSearch(queryStrBldr);
232 //TODO: add tenant id
233 emf = getEntityManagerFactory();
234 em = emf.createEntityManager();
235 String queryStr = queryStrBldr.toString(); //for debugging
236 Query q = em.createQuery(queryStr);
238 for (DocumentFilter.ParamBinding p : params) {
239 q.setParameter(p.getName(), p.getValue());
241 if (docFilter.getOffset() > 0) {
242 q.setFirstResult(docFilter.getOffset());
244 if (docFilter.getPageSize() > 0) {
245 q.setMaxResults(docFilter.getPageSize());
248 //FIXME is transaction required for get?
249 em.getTransaction().begin();
250 List list = q.getResultList();
251 em.getTransaction().commit();
252 DocumentWrapper<List> wrapDoc = new DocumentWrapperImpl<List>(list);
253 handler.handle(Action.GET_ALL, wrapDoc);
254 handler.complete(Action.GET_ALL, wrapDoc);
255 } catch (DocumentException de) {
257 } catch (Exception e) {
258 if (logger.isDebugEnabled()) {
259 logger.debug("Caught exception ", e);
261 throw new DocumentException(e);
264 releaseEntityManagerFactory(emf);
270 * @see org.collectionspace.services.common.storage.StorageClient#update(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
273 public void update(ServiceContext ctx, String id, DocumentHandler handler)
274 throws BadRequestException, DocumentNotFoundException,
276 String docType = ctx.getDocumentType();
277 if (docType == null) {
278 throw new DocumentNotFoundException(
279 "Unable to find DocumentType for service " + ctx.getServiceName());
281 if (handler == null) {
282 throw new IllegalArgumentException(
283 "JpaStorageClient.update: handler is missing");
285 EntityManagerFactory emf = null;
286 EntityManager em = null;
288 handler.prepare(Action.UPDATE);
289 Object entity = handler.getCommonPart();
291 DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entity);
292 handler.handle(Action.UPDATE, wrapDoc);
293 emf = getEntityManagerFactory();
294 em = emf.createEntityManager();
295 em.getTransaction().begin();
296 Object entityFound = em.find(entity.getClass(), id);
297 if (entityFound == null) {
298 if (em != null && em.getTransaction().isActive()) {
299 em.getTransaction().rollback();
301 String msg = "could not find entity with id=" + id;
303 throw new DocumentNotFoundException(msg);
306 em.getTransaction().commit();
307 handler.complete(Action.UPDATE, wrapDoc);
308 } catch (DocumentException de) {
310 } catch (Exception e) {
311 if (logger.isDebugEnabled()) {
312 logger.debug("Caught exception ", e);
314 throw new DocumentException(e);
317 releaseEntityManagerFactory(emf);
323 * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
326 public void delete(ServiceContext ctx, String id)
327 throws DocumentNotFoundException,
330 if (logger.isDebugEnabled()) {
331 logger.debug("deleting entity with id=" + id);
333 String docType = ctx.getDocumentType();
334 if (docType == null) {
335 throw new DocumentNotFoundException(
336 "Unable to find DocumentType for service " + ctx.getServiceName());
338 EntityManagerFactory emf = null;
339 EntityManager em = null;
341 StringBuilder deleteStr = new StringBuilder("DELETE FROM ");
342 deleteStr.append(getEntityName(ctx));
343 deleteStr.append(" WHERE csid = :csid");
344 //TODO: add tenant id
346 emf = getEntityManagerFactory();
347 em = emf.createEntityManager();
348 Query q = em.createQuery(deleteStr.toString());
349 q.setParameter("csid", id);
350 //TODO: add tenant id
352 em.getTransaction().begin();
353 rcount = q.executeUpdate();
355 if (em != null && em.getTransaction().isActive()) {
356 em.getTransaction().rollback();
358 String msg = "could not find entity with id=" + id;
360 throw new DocumentNotFoundException(msg);
362 em.getTransaction().commit();
364 } catch (DocumentException de) {
366 } catch (Exception e) {
367 if (logger.isDebugEnabled()) {
368 logger.debug("Caught exception ", e);
370 if (em != null && em.getTransaction().isActive()) {
371 em.getTransaction().rollback();
373 throw new DocumentException(e);
376 releaseEntityManagerFactory(emf);
382 * Gets the entity manager factory.
384 * @return the entity manager factory
386 public EntityManagerFactory getEntityManagerFactory() {
387 return getEntityManagerFactory(CS_PERSISTENCE_UNIT);
391 * Gets the entity manager factory.
393 * @param persistenceUnit the persistence unit
395 * @return the entity manager factory
397 public EntityManagerFactory getEntityManagerFactory(
398 String persistenceUnit) {
399 return Persistence.createEntityManagerFactory(persistenceUnit);
404 * Release entity manager factory.
408 public void releaseEntityManagerFactory(EntityManagerFactory emf) {
416 * getValue gets invokes specified accessor method on given object. Assumption
417 * is that this is used for JavaBean pattern getXXX methods only.
418 * @param o object to return value from
419 * @param methodName of method to invoke
420 * @return value returned of invocation
421 * @throws NoSuchMethodException
422 * @throws IllegalAccessException
423 * @throws InvocationTargetException
425 protected Object getValue(Object o, String methodName)
426 throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
427 if (methodName == null) {
428 String msg = methodName + " cannot be null";
430 throw new IllegalArgumentException(msg);
432 Class c = o.getClass();
433 Method m = c.getMethod(methodName);
435 Object r = m.invoke(o);
436 if (logger.isDebugEnabled()) {
437 logger.debug("getValue returned value=" + r
438 + " for " + c.getName());
444 * setValue mutates the given object by invoking specified method. Assumption
445 * is that this is used for JavaBean pattern setXXX methods only.
446 * @param o object to mutate
447 * @param methodName indicates method to invoke
448 * @param argType type of the only argument (assumed) to method
449 * @param argValue value of the only argument (assumed) to method
451 * @throws NoSuchMethodException
452 * @throws IllegalAccessException
453 * @throws InvocationTargetException
455 protected Object setValue(Object o, String methodName, Class argType, Object argValue)
456 throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
457 if (methodName == null) {
458 String msg = methodName + " cannot be null";
460 throw new IllegalArgumentException(msg);
462 if (argType == null) {
463 String msg = "argType cannot be null";
465 throw new IllegalArgumentException(msg);
467 Class c = o.getClass();
468 Method m = c.getMethod(methodName, argType);
469 Object r = m.invoke(o, argValue);
470 if (logger.isDebugEnabled()) {
471 logger.debug("completed invocation of " + methodName
472 + " for " + c.getName());
481 * @param csid the csid
483 * @throws Exception the exception
485 protected void setCsid(Object o, String csid) throws Exception {
487 String id = (String) getValue(o, "getCsid");
489 if (!id.equals(csid)) {
490 String msg = "Csids do not match!";
492 throw new BadRequestException(msg);
500 setValue(o, "setCsid", java.lang.String.class, csid);
504 * Gets the entity name.
508 * @return the entity name
510 protected String getEntityName(ServiceContext ctx) {
511 Object o = ctx.getProperty("entity-name");
513 throw new IllegalArgumentException("property entity-name missing in context "
521 public void get(ServiceContext ctx, DocumentHandler handler)
522 throws DocumentNotFoundException, DocumentException {
523 throw new UnsupportedOperationException();