]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
03286a81f377dbf07f86912582bab38bfbec3020
[tmp/jakarta-migration.git] /
1 /**
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:
5
6  *  http://www.collectionspace.org
7  *  http://wiki.collectionspace.org
8
9  *  Copyright 2009 University of California at Berkeley
10
11  *  Licensed under the Educational Community License (ECL), Version 2.0.
12  *  You may not use this file except in compliance with this License.
13
14  *  You may obtain a copy of the ECL 2.0 License at
15
16  *  https://source.collectionspace.org/collection-space/LICENSE.txt
17  */
18 package org.collectionspace.services.common.storage.jpa;
19
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;
41
42 /**
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.
47  * 
48  * Assumption: each persistent entity has the following 3 attributes
49 <xs:element name="createdAt" type="xs:dateTime">
50 <xs:annotation>
51 <xs:appinfo>
52 <hj:basic>
53 <orm:column name="created_at" nullable="false"/>
54 </hj:basic>
55 </xs:appinfo>
56 </xs:annotation>
57 </xs:element>
58 <xs:element name="updatedAt" type="xs:dateTime">
59 <xs:annotation>
60 <xs:appinfo>
61 <hj:basic>
62 <orm:column name="updated_at" />
63 </hj:basic>
64 </xs:appinfo>
65 </xs:annotation>
66 </xs:element>
67 </xs:sequence>
68 <xs:attribute name="csid" type="xs:string">
69 <xs:annotation>
70 <xs:appinfo>
71 <hj:id>
72 <orm:column name="csid" length="128" nullable="false"/>
73 </hj:id>
74 </xs:appinfo>
75 </xs:annotation>
76 </xs:attribute>
77  *
78  * $LastChangedRevision: $ $LastChangedDate: $
79  */
80 public class JpaStorageClientImpl implements StorageClient {
81
82     /** The logger. */
83     private final Logger logger = LoggerFactory.getLogger(JpaStorageClientImpl.class);
84     private Class entityClazz;
85
86     /**
87      * Instantiates a new jpa storage client.
88      */
89     public JpaStorageClientImpl() {
90     }
91
92     public JpaStorageClientImpl(Class entityClazz) {
93         this.entityClazz = entityClazz;
94     }
95
96     /* (non-Javadoc)
97      * @see org.collectionspace.services.common.storage.StorageClient#create(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
98      */
99     @Override
100     public String create(ServiceContext ctx,
101             DocumentHandler handler) throws BadRequestException,
102             DocumentException {
103
104         if (ctx == null) {
105             throw new IllegalArgumentException(
106                     "JpaStorageClient.create: ctx is missing");
107         }
108         if (handler == null) {
109             throw new IllegalArgumentException(
110                     "JpaStorageClient.create: handler is missing");
111         }
112         EntityManagerFactory emf = null;
113         EntityManager em = null;
114         try {
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();
123             em.persist(entity);
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();
130             }
131             throw bre;
132         } catch (DocumentException de) {
133             throw de;
134         } catch (Exception e) {
135             if (em != null && em.getTransaction().isActive()) {
136                 em.getTransaction().rollback();
137             }
138             if (logger.isDebugEnabled()) {
139                 logger.debug("Caught exception ", e);
140             }
141             throw new DocumentException(e);
142         } finally {
143             if (em != null) {
144                 JpaStorageUtils.releaseEntityManagerFactory(emf);
145             }
146         }
147
148     }
149
150     /* (non-Javadoc)
151      * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.util.List, org.collectionspace.services.common.document.DocumentHandler)
152      */
153     @Override
154     public void get(ServiceContext ctx, List<String> csidList, DocumentHandler handler)
155             throws DocumentNotFoundException, DocumentException {
156         throw new UnsupportedOperationException();
157     }
158
159     /* (non-Javadoc)
160      * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
161      */
162     @Override
163     public void get(ServiceContext ctx, String id, DocumentHandler handler)
164             throws DocumentNotFoundException, DocumentException {
165         if (ctx == null) {
166             throw new IllegalArgumentException(
167                     "JpaStorageClient.get: ctx is missing");
168         }
169         if (handler == null) {
170             throw new IllegalArgumentException(
171                     "JpaStorageClient.get: handler is missing");
172         }
173         DocumentFilter docFilter = handler.getDocumentFilter();
174         if (docFilter == null) {
175             docFilter = handler.createDocumentFilter();
176         }
177         EntityManagerFactory emf = null;
178         EntityManager em = null;
179         try {
180             handler.prepare(Action.GET);
181             Object o = null;
182             try {
183                 o = JpaStorageUtils.getEntity(getEntityName(ctx), id, docFilter);
184             } catch (NoResultException nre) {
185                 if (em != null && em.getTransaction().isActive()) {
186                     em.getTransaction().rollback();
187                 }
188                 String msg = "could not find entity with id=" + id;
189                 logger.error(msg, nre);
190                 throw new DocumentNotFoundException(msg, nre);
191             }
192             DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(o);
193             handler.handle(Action.GET, wrapDoc);
194             handler.complete(Action.GET, wrapDoc);
195         } catch (DocumentException de) {
196             throw de;
197         } catch (Exception e) {
198             if (logger.isDebugEnabled()) {
199                 logger.debug("Caught exception ", e);
200             }
201             throw new DocumentException(e);
202         } finally {
203             if (emf != null) {
204                 JpaStorageUtils.releaseEntityManagerFactory(emf);
205             }
206         }
207     }
208
209     /* (non-Javadoc)
210      * @see org.collectionspace.services.common.storage.StorageClient#getAll(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
211      */
212     @Override
213     public void getAll(ServiceContext ctx, DocumentHandler handler)
214             throws DocumentNotFoundException, DocumentException {
215         throw new UnsupportedOperationException("use getFiltered instead");
216     }
217
218     /* (non-Javadoc)
219      * @see org.collectionspace.services.common.storage.StorageClient#getFiltered(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
220      */
221     @Override
222     public void getFiltered(ServiceContext ctx, DocumentHandler handler)
223             throws DocumentNotFoundException, DocumentException {
224         if (ctx == null) {
225             throw new IllegalArgumentException(
226                     "JpaStorageClient.getFiltered: ctx is missing");
227         }
228         if (handler == null) {
229             throw new IllegalArgumentException(
230                     "JpaStorageClient.getFiltered: handler is missing");
231         }
232
233         DocumentFilter docFilter = handler.getDocumentFilter();
234         if (docFilter == null) {
235             docFilter = handler.createDocumentFilter();
236         }
237         EntityManagerFactory emf = null;
238         EntityManager em = null;
239         try {
240             handler.prepare(Action.GET_ALL);
241
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);
251             //bind parameters
252             for (DocumentFilter.ParamBinding p : params) {
253                 q.setParameter(p.getName(), p.getValue());
254             }
255             if (docFilter.getOffset() > 0) {
256                 q.setFirstResult(docFilter.getOffset());
257             }
258             if (docFilter.getPageSize() > 0) {
259                 q.setMaxResults(docFilter.getPageSize());
260             }
261
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) {
270             throw de;
271         } catch (Exception e) {
272             if (logger.isDebugEnabled()) {
273                 logger.debug("Caught exception ", e);
274             }
275             throw new DocumentException(e);
276         } finally {
277             if (emf != null) {
278                 JpaStorageUtils.releaseEntityManagerFactory(emf);
279             }
280         }
281     }
282
283     /* (non-Javadoc)
284      * @see org.collectionspace.services.common.storage.StorageClient#update(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
285      */
286     @Override
287     public void update(ServiceContext ctx, String id, DocumentHandler handler)
288             throws BadRequestException, DocumentNotFoundException,
289             DocumentException {
290         if (ctx == null) {
291             throw new IllegalArgumentException(
292                     "JpaStorageClient.update: ctx is missing");
293         }
294         if (handler == null) {
295             throw new IllegalArgumentException(
296                     "JpaStorageClient.update: handler is missing");
297         }
298         EntityManagerFactory emf = null;
299         EntityManager em = null;
300         try {
301             handler.prepare(Action.UPDATE);
302             Object entity = handler.getCommonPart();
303             setCsid(entity, id);
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();
313                 }
314                 String msg = "could not find entity with id=" + id;
315                 logger.error(msg);
316                 throw new DocumentNotFoundException(msg);
317             }
318             entity = em.merge(entity);
319             setValue(entity, "setUpdatedAtItem", Date.class, new Date());
320             if (logger.isDebugEnabled()) {
321                 logger.debug("merged entity=" + entity.toString());
322             }
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();
328             }
329             throw bre;
330         } catch (DocumentException de) {
331             throw de;
332         } catch (Exception e) {
333             if (logger.isDebugEnabled()) {
334                 logger.debug("Caught exception ", e);
335             }
336             throw new DocumentException(e);
337         } finally {
338             if (emf != null) {
339                 JpaStorageUtils.releaseEntityManagerFactory(emf);
340             }
341         }
342     }
343
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)
346      */
347     @Override
348     public void delete(ServiceContext ctx, String id)
349             throws DocumentNotFoundException,
350             DocumentException {
351
352         if (ctx == null) {
353             throw new IllegalArgumentException(
354                     "JpaStorageClient.delete: ctx is missing");
355         }
356
357         if (logger.isDebugEnabled()) {
358             logger.debug("deleting entity with id=" + id);
359         }
360         EntityManagerFactory emf = null;
361         EntityManager em = null;
362         try {
363
364             //TODO: add tenant id
365
366             emf = JpaStorageUtils.getEntityManagerFactory();
367             em = emf.createEntityManager();
368
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();
374                 }
375                 String msg = "could not find entity with id=" + id;
376                 logger.error(msg);
377                 throw new DocumentNotFoundException(msg);
378             }
379             em.remove(entityFound);
380             em.getTransaction().commit();
381
382         } catch (DocumentException de) {
383             throw de;
384         } catch (Exception e) {
385             if (logger.isDebugEnabled()) {
386                 logger.debug("Caught exception ", e);
387             }
388             if (em != null && em.getTransaction().isActive()) {
389                 em.getTransaction().rollback();
390             }
391             throw new DocumentException(e);
392         } finally {
393             if (emf != null) {
394                 JpaStorageUtils.releaseEntityManagerFactory(emf);
395             }
396         }
397     }
398
399     /**
400      * deleteWhere uses the where clause to delete an entity represented by the id
401      * it does not delete any child entities.
402      * @param ctx
403      * @param id
404      * @throws DocumentNotFoundException
405      * @throws DocumentException
406      */
407     public void deleteWhere(ServiceContext ctx, String id)
408             throws DocumentNotFoundException,
409             DocumentException {
410
411         if (ctx == null) {
412             throw new IllegalArgumentException(
413                     "JpaStorageClient.deleteWhere: ctx is missing");
414         }
415
416         if (logger.isDebugEnabled()) {
417             logger.debug("deleting entity with id=" + id);
418         }
419         EntityManagerFactory emf = null;
420         EntityManager em = null;
421         try {
422             StringBuilder deleteStr = new StringBuilder("DELETE FROM ");
423             deleteStr.append(getEntityName(ctx));
424             deleteStr.append(" WHERE csid = :csid");
425             //TODO: add tenant id
426
427             emf = JpaStorageUtils.getEntityManagerFactory();
428             em = emf.createEntityManager();
429             Query q = em.createQuery(deleteStr.toString());
430             q.setParameter("csid", id);
431
432             int rcount = 0;
433             em.getTransaction().begin();
434             rcount = q.executeUpdate();
435             if (rcount != 1) {
436                 if (em != null && em.getTransaction().isActive()) {
437                     em.getTransaction().rollback();
438                 }
439                 String msg = "could not find entity with id=" + id;
440                 logger.error(msg);
441                 throw new DocumentNotFoundException(msg);
442             }
443             em.getTransaction().commit();
444
445         } catch (DocumentException de) {
446             throw de;
447         } catch (Exception e) {
448             if (logger.isDebugEnabled()) {
449                 logger.debug("Caught exception ", e);
450             }
451             if (em != null && em.getTransaction().isActive()) {
452                 em.getTransaction().rollback();
453             }
454             throw new DocumentException(e);
455         } finally {
456             if (emf != null) {
457                 JpaStorageUtils.releaseEntityManagerFactory(emf);
458             }
459         }
460     }
461
462
463
464     /**
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
473      */
474     protected Object getValue(Object o, String methodName)
475             throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
476         if (methodName == null) {
477             String msg = methodName + " cannot be null";
478             logger.error(msg);
479             throw new IllegalArgumentException(msg);
480         }
481         Class c = o.getClass();
482         Method m = c.getMethod(methodName);
483
484         Object r = m.invoke(o);
485         if (logger.isDebugEnabled()) {
486             logger.debug("getValue returned value=" + r
487                     + " for " + c.getName());
488         }
489         return r;
490     }
491
492     /**
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
499      * @return
500      * @throws NoSuchMethodException
501      * @throws IllegalAccessException
502      * @throws InvocationTargetException
503      */
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";
508             logger.error(msg);
509             throw new IllegalArgumentException(msg);
510         }
511         if (argType == null) {
512             String msg = "argType cannot be null";
513             logger.error(msg);
514             throw new IllegalArgumentException(msg);
515         }
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());
522         }
523         return r;
524     }
525
526     /**
527      * Sets the csid.
528      * 
529      * @param o the o
530      * @param csid the csid
531      * 
532      * @throws Exception the exception
533      */
534     protected void setCsid(Object o, String csid) throws Exception {
535         //verify csid
536         String id = (String) getValue(o, "getCsid");
537         if (id != null) {
538             if (!id.equals(csid)) {
539                 String msg = "Csids do not match!";
540                 logger.error(msg);
541                 throw new BadRequestException(msg);
542             } else {
543                 //no need to set
544                 return;
545             }
546
547         }
548         //set csid
549         setValue(o, "setCsid", java.lang.String.class, csid);
550     }
551
552     /**
553      * Gets the entity name.
554      * 
555      * @param ctx the ctx
556      * 
557      * @return the entity name
558      */
559     protected String getEntityName(ServiceContext ctx) {
560         Object o = ctx.getProperty("entity-name");
561         if (o == null) {
562             throw new IllegalArgumentException("property entity-name missing in context "
563                     + ctx.toString());
564         }
565
566         return (String) o;
567     }
568
569     /**
570      * getEntity returns persistent entity for given id. it assumes that
571      * JpaStorageClientImpl is implemented using the JpaStorageClientImpl(entityClazz)
572      * constructor
573      * @param em
574      * @param id
575      * @return
576      * @throws DocumentNotFoundException
577      * @throws UnsupportedOperationException if JpaStorageClientImpl is not implemented
578      * using the JpaStorageClientImpl(entityClazz)
579      * constructor
580      */
581     protected Object getEntity(EntityManager em, String id) throws DocumentNotFoundException {
582         if (entityClazz == null) {
583             String msg = "Not constructed with JpaStorageClientImpl(entityClazz) ctor";
584             logger.error(msg);
585             throw new UnsupportedOperationException(msg);
586         }
587         Object entityFound = em.find(entityClazz, id);
588         if (entityFound == null) {
589             if (em != null && em.getTransaction().isActive()) {
590                 em.getTransaction().rollback();
591             }
592             String msg = "could not find entity of type=" + entityClazz.getName()
593                     + " with id=" + id;
594             logger.error(msg);
595             throw new DocumentNotFoundException(msg);
596         }
597         return entityFound;
598     }
599
600     @Override
601     public void get(ServiceContext ctx, DocumentHandler handler)
602             throws DocumentNotFoundException, DocumentException {
603         throw new UnsupportedOperationException();
604     }
605 }