]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
552b17e442c4ca3b86b989b2c91ae560a8dbfd81
[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     /** The Constant CS_PERSISTENCE_UNIT. */
85     public final static String CS_PERSISTENCE_UNIT = "org.collectionspace.services";
86     private Class entityClazz;
87
88     /**
89      * Instantiates a new jpa storage client.
90      */
91     public JpaStorageClientImpl() {
92     }
93
94     public JpaStorageClientImpl(Class entityClazz) {
95         this.entityClazz = entityClazz;
96     }
97
98     /* (non-Javadoc)
99      * @see org.collectionspace.services.common.storage.StorageClient#create(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
100      */
101     @Override
102     public String create(ServiceContext ctx,
103             DocumentHandler handler) throws BadRequestException,
104             DocumentException {
105
106         if (ctx == null) {
107             throw new IllegalArgumentException(
108                     "JpaStorageClient.create: ctx is missing");
109         }
110         if (handler == null) {
111             throw new IllegalArgumentException(
112                     "JpaStorageClient.create: handler is missing");
113         }
114         EntityManagerFactory emf = null;
115         EntityManager em = null;
116         try {
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();
125             em.persist(entity);
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();
132             }
133             throw bre;
134         } catch (DocumentException de) {
135             throw de;
136         } catch (Exception e) {
137             if (em != null && em.getTransaction().isActive()) {
138                 em.getTransaction().rollback();
139             }
140             if (logger.isDebugEnabled()) {
141                 logger.debug("Caught exception ", e);
142             }
143             throw new DocumentException(e);
144         } finally {
145             if (em != null) {
146                 releaseEntityManagerFactory(emf);
147             }
148         }
149
150     }
151
152     /* (non-Javadoc)
153      * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.util.List, org.collectionspace.services.common.document.DocumentHandler)
154      */
155     @Override
156     public void get(ServiceContext ctx, List<String> csidList, DocumentHandler handler)
157             throws DocumentNotFoundException, DocumentException {
158         throw new UnsupportedOperationException();
159     }
160
161     /* (non-Javadoc)
162      * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
163      */
164     @Override
165     public void get(ServiceContext ctx, String id, DocumentHandler handler)
166             throws DocumentNotFoundException, DocumentException {
167         if (ctx == null) {
168             throw new IllegalArgumentException(
169                     "JpaStorageClient.get: ctx is missing");
170         }
171         if (handler == null) {
172             throw new IllegalArgumentException(
173                     "JpaStorageClient.get: handler is missing");
174         }
175         DocumentFilter docFilter = handler.getDocumentFilter();
176         if (docFilter == null) {
177             docFilter = handler.createDocumentFilter();
178         }
179         EntityManagerFactory emf = null;
180         EntityManager em = null;
181         try {
182             handler.prepare(Action.GET);
183             StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
184             queryStrBldr.append(getEntityName(ctx));
185             queryStrBldr.append(" a");
186             queryStrBldr.append(" WHERE csid = :csid");
187             //TODO: add tenant id
188             String where = docFilter.getWhereClause();
189             if ((null != where) && (where.length() > 0)) {
190                 queryStrBldr.append(" AND " + where);
191             }
192             emf = getEntityManagerFactory();
193             em = emf.createEntityManager();
194             String queryStr = queryStrBldr.toString(); //for debugging
195             Query q = em.createQuery(queryStr);
196             q.setParameter("csid", id);
197             //TODO: add tenant id
198
199             //TODO: get page
200             if ((docFilter.getOffset() > 0) || (docFilter.getPageSize() > 0)) {
201             } else {
202             }
203             Object o = null;
204
205             try {
206                 //require transaction for get?
207                 em.getTransaction().begin();
208                 o = q.getSingleResult();
209                 em.getTransaction().commit();
210             } catch (NoResultException nre) {
211                 if (em != null && em.getTransaction().isActive()) {
212                     em.getTransaction().rollback();
213                 }
214                 String msg = "could not find entity with id=" + id;
215                 logger.error(msg, nre);
216                 throw new DocumentNotFoundException(msg, nre);
217             }
218             DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(o);
219             handler.handle(Action.GET, wrapDoc);
220             handler.complete(Action.GET, wrapDoc);
221         } catch (DocumentException de) {
222             throw de;
223         } catch (Exception e) {
224             if (logger.isDebugEnabled()) {
225                 logger.debug("Caught exception ", e);
226             }
227             throw new DocumentException(e);
228         } finally {
229             if (emf != null) {
230                 releaseEntityManagerFactory(emf);
231             }
232         }
233     }
234
235     /* (non-Javadoc)
236      * @see org.collectionspace.services.common.storage.StorageClient#getAll(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
237      */
238     @Override
239     public void getAll(ServiceContext ctx, DocumentHandler handler)
240             throws DocumentNotFoundException, DocumentException {
241         throw new UnsupportedOperationException("use getFiltered instead");
242     }
243
244     /* (non-Javadoc)
245      * @see org.collectionspace.services.common.storage.StorageClient#getFiltered(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
246      */
247     @Override
248     public void getFiltered(ServiceContext ctx, DocumentHandler handler)
249             throws DocumentNotFoundException, DocumentException {
250         if (ctx == null) {
251             throw new IllegalArgumentException(
252                     "JpaStorageClient.getFiltered: ctx is missing");
253         }
254         if (handler == null) {
255             throw new IllegalArgumentException(
256                     "JpaStorageClient.getFiltered: handler is missing");
257         }
258         DocumentFilter docFilter = handler.getDocumentFilter();
259         if (docFilter == null) {
260             docFilter = handler.createDocumentFilter();
261         }
262         EntityManagerFactory emf = null;
263         EntityManager em = null;
264         try {
265             handler.prepare(Action.GET_ALL);
266
267             StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
268             queryStrBldr.append(getEntityName(ctx));
269             queryStrBldr.append(" a");
270             List<DocumentFilter.ParamBinding> params = docFilter.buildWhereForSearch(queryStrBldr);
271             //TODO: add tenant id
272             emf = getEntityManagerFactory();
273             em = emf.createEntityManager();
274             String queryStr = queryStrBldr.toString(); //for debugging
275             Query q = em.createQuery(queryStr);
276             //bind parameters
277             for (DocumentFilter.ParamBinding p : params) {
278                 q.setParameter(p.getName(), p.getValue());
279             }
280             if (docFilter.getOffset() > 0) {
281                 q.setFirstResult(docFilter.getOffset());
282             }
283             if (docFilter.getPageSize() > 0) {
284                 q.setMaxResults(docFilter.getPageSize());
285             }
286
287             //FIXME is transaction required for get?
288             em.getTransaction().begin();
289             List list = q.getResultList();
290             em.getTransaction().commit();
291             DocumentWrapper<List> wrapDoc = new DocumentWrapperImpl<List>(list);
292             handler.handle(Action.GET_ALL, wrapDoc);
293             handler.complete(Action.GET_ALL, wrapDoc);
294         } catch (DocumentException de) {
295             throw de;
296         } catch (Exception e) {
297             if (logger.isDebugEnabled()) {
298                 logger.debug("Caught exception ", e);
299             }
300             throw new DocumentException(e);
301         } finally {
302             if (emf != null) {
303                 releaseEntityManagerFactory(emf);
304             }
305         }
306     }
307
308     /* (non-Javadoc)
309      * @see org.collectionspace.services.common.storage.StorageClient#update(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
310      */
311     @Override
312     public void update(ServiceContext ctx, String id, DocumentHandler handler)
313             throws BadRequestException, DocumentNotFoundException,
314             DocumentException {
315         if (ctx == null) {
316             throw new IllegalArgumentException(
317                     "JpaStorageClient.update: ctx is missing");
318         }
319         if (handler == null) {
320             throw new IllegalArgumentException(
321                     "JpaStorageClient.update: handler is missing");
322         }
323         EntityManagerFactory emf = null;
324         EntityManager em = null;
325         try {
326             handler.prepare(Action.UPDATE);
327             Object entity = handler.getCommonPart();
328             setCsid(entity, id);
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();
338                 }
339                 String msg = "could not find entity with id=" + id;
340                 logger.error(msg);
341                 throw new DocumentNotFoundException(msg);
342             }
343             entity = em.merge(entity);
344             setValue(entity, "setUpdatedAtItem", Date.class, new Date());
345             if (logger.isDebugEnabled()) {
346                 logger.debug("merged entity=" + entity.toString());
347             }
348             em.getTransaction().commit();
349             handler.complete(Action.UPDATE, wrapDoc);
350         } catch (BadRequestException bre) {
351             if (em != null && em.getTransaction().isActive()) {
352                 em.getTransaction().rollback();
353             }
354             throw bre;
355         } catch (DocumentException de) {
356             throw de;
357         } catch (Exception e) {
358             if (logger.isDebugEnabled()) {
359                 logger.debug("Caught exception ", e);
360             }
361             throw new DocumentException(e);
362         } finally {
363             if (emf != null) {
364                 releaseEntityManagerFactory(emf);
365             }
366         }
367     }
368
369     /* delete use delete to remove parent entity along with child entities
370      * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
371      */
372     @Override
373     public void delete(ServiceContext ctx, String id)
374             throws DocumentNotFoundException,
375             DocumentException {
376
377         if (ctx == null) {
378             throw new IllegalArgumentException(
379                     "JpaStorageClient.delete: ctx is missing");
380         }
381
382         if (logger.isDebugEnabled()) {
383             logger.debug("deleting entity with id=" + id);
384         }
385         EntityManagerFactory emf = null;
386         EntityManager em = null;
387         try {
388
389             //TODO: add tenant id
390
391             emf = getEntityManagerFactory();
392             em = emf.createEntityManager();
393
394             em.getTransaction().begin();
395             Object entityFound = getEntity(em, id);
396             if (entityFound == null) {
397                 if (em != null && em.getTransaction().isActive()) {
398                     em.getTransaction().rollback();
399                 }
400                 String msg = "could not find entity with id=" + id;
401                 logger.error(msg);
402                 throw new DocumentNotFoundException(msg);
403             }
404             em.remove(entityFound);
405             em.getTransaction().commit();
406
407         } catch (DocumentException de) {
408             throw de;
409         } catch (Exception e) {
410             if (logger.isDebugEnabled()) {
411                 logger.debug("Caught exception ", e);
412             }
413             if (em != null && em.getTransaction().isActive()) {
414                 em.getTransaction().rollback();
415             }
416             throw new DocumentException(e);
417         } finally {
418             if (emf != null) {
419                 releaseEntityManagerFactory(emf);
420             }
421         }
422     }
423
424     /**
425      * deleteWhere uses the where clause to delete an entity represented by the id
426      * it does not delete any child entities.
427      * @param ctx
428      * @param id
429      * @throws DocumentNotFoundException
430      * @throws DocumentException
431      */
432     public void deleteWhere(ServiceContext ctx, String id)
433             throws DocumentNotFoundException,
434             DocumentException {
435
436         if (ctx == null) {
437             throw new IllegalArgumentException(
438                     "JpaStorageClient.deleteWhere: ctx is missing");
439         }
440
441         if (logger.isDebugEnabled()) {
442             logger.debug("deleting entity with id=" + id);
443         }
444         EntityManagerFactory emf = null;
445         EntityManager em = null;
446         try {
447             StringBuilder deleteStr = new StringBuilder("DELETE FROM ");
448             deleteStr.append(getEntityName(ctx));
449             deleteStr.append(" WHERE csid = :csid");
450             //TODO: add tenant id
451
452             emf = getEntityManagerFactory();
453             em = emf.createEntityManager();
454             Query q = em.createQuery(deleteStr.toString());
455             q.setParameter("csid", id);
456
457             int rcount = 0;
458             em.getTransaction().begin();
459             rcount = q.executeUpdate();
460             if (rcount != 1) {
461                 if (em != null && em.getTransaction().isActive()) {
462                     em.getTransaction().rollback();
463                 }
464                 String msg = "could not find entity with id=" + id;
465                 logger.error(msg);
466                 throw new DocumentNotFoundException(msg);
467             }
468             em.getTransaction().commit();
469
470         } catch (DocumentException de) {
471             throw de;
472         } catch (Exception e) {
473             if (logger.isDebugEnabled()) {
474                 logger.debug("Caught exception ", e);
475             }
476             if (em != null && em.getTransaction().isActive()) {
477                 em.getTransaction().rollback();
478             }
479             throw new DocumentException(e);
480         } finally {
481             if (emf != null) {
482                 releaseEntityManagerFactory(emf);
483             }
484         }
485     }
486
487     /**
488      * Gets the entity manager factory.
489      * 
490      * @return the entity manager factory
491      */
492     public EntityManagerFactory getEntityManagerFactory() {
493         return getEntityManagerFactory(CS_PERSISTENCE_UNIT);
494     }
495
496     /**
497      * Gets the entity manager factory.
498      * 
499      * @param persistenceUnit the persistence unit
500      * 
501      * @return the entity manager factory
502      */
503     public EntityManagerFactory getEntityManagerFactory(
504             String persistenceUnit) {
505         return Persistence.createEntityManagerFactory(persistenceUnit);
506
507     }
508
509     /**
510      * Release entity manager factory.
511      * 
512      * @param emf the emf
513      */
514     public void releaseEntityManagerFactory(EntityManagerFactory emf) {
515         if (emf != null) {
516             emf.close();
517         }
518
519     }
520
521     /**
522      * getValue gets invokes specified accessor method on given object. Assumption
523      * is that this is used for JavaBean pattern getXXX methods only.
524      * @param o object to return value from
525      * @param methodName of method to invoke
526      * @return value returned of invocation
527      * @throws NoSuchMethodException
528      * @throws IllegalAccessException
529      * @throws InvocationTargetException
530      */
531     protected Object getValue(Object o, String methodName)
532             throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
533         if (methodName == null) {
534             String msg = methodName + " cannot be null";
535             logger.error(msg);
536             throw new IllegalArgumentException(msg);
537         }
538         Class c = o.getClass();
539         Method m = c.getMethod(methodName);
540
541         Object r = m.invoke(o);
542         if (logger.isDebugEnabled()) {
543             logger.debug("getValue returned value=" + r
544                     + " for " + c.getName());
545         }
546         return r;
547     }
548
549     /**
550      * setValue mutates the given object by invoking specified method. Assumption
551      * is that this is used for JavaBean pattern setXXX methods only.
552      * @param o object to mutate
553      * @param methodName indicates method to invoke
554      * @param argType type of the only argument (assumed) to method
555      * @param argValue value of the only argument (assumed) to method
556      * @return
557      * @throws NoSuchMethodException
558      * @throws IllegalAccessException
559      * @throws InvocationTargetException
560      */
561     protected Object setValue(Object o, String methodName, Class argType, Object argValue)
562             throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
563         if (methodName == null) {
564             String msg = methodName + " cannot be null";
565             logger.error(msg);
566             throw new IllegalArgumentException(msg);
567         }
568         if (argType == null) {
569             String msg = "argType cannot be null";
570             logger.error(msg);
571             throw new IllegalArgumentException(msg);
572         }
573         Class c = o.getClass();
574         Method m = c.getMethod(methodName, argType);
575         Object r = m.invoke(o, argValue);
576         if (logger.isDebugEnabled()) {
577             logger.debug("completed invocation of " + methodName
578                     + " for " + c.getName());
579         }
580         return r;
581     }
582
583     /**
584      * Sets the csid.
585      * 
586      * @param o the o
587      * @param csid the csid
588      * 
589      * @throws Exception the exception
590      */
591     protected void setCsid(Object o, String csid) throws Exception {
592         //verify csid
593         String id = (String) getValue(o, "getCsid");
594         if (id != null) {
595             if (!id.equals(csid)) {
596                 String msg = "Csids do not match!";
597                 logger.error(msg);
598                 throw new BadRequestException(msg);
599             } else {
600                 //no need to set
601                 return;
602             }
603
604         }
605         //set csid
606         setValue(o, "setCsid", java.lang.String.class, csid);
607     }
608
609     /**
610      * Gets the entity name.
611      * 
612      * @param ctx the ctx
613      * 
614      * @return the entity name
615      */
616     protected String getEntityName(ServiceContext ctx) {
617         Object o = ctx.getProperty("entity-name");
618         if (o == null) {
619             throw new IllegalArgumentException("property entity-name missing in context "
620                     + ctx.toString());
621         }
622
623         return (String) o;
624     }
625
626     /**
627      * getEntity returns persistent entity for given id. it assumes that
628      * JpaStorageClientImpl is implemented using the JpaStorageClientImpl(entityClazz)
629      * constructor
630      * @param em
631      * @param id
632      * @return
633      * @throws DocumentNotFoundException
634      * @throws UnsupportedOperationException if JpaStorageClientImpl is not implemented
635      * using the JpaStorageClientImpl(entityClazz)
636      * constructor
637      */
638     protected Object getEntity(EntityManager em, String id) throws DocumentNotFoundException {
639         if (entityClazz == null) {
640             String msg = "Not constructed with JpaStorageClientImpl(entityClazz) ctor";
641             logger.error(msg);
642             throw new UnsupportedOperationException(msg);
643         }
644         Object entityFound = em.find(entityClazz, id);
645         if (entityFound == null) {
646             if (em != null && em.getTransaction().isActive()) {
647                 em.getTransaction().rollback();
648             }
649             String msg = "could not find entity of type=" + entityClazz.getName()
650                     + " with id=" + id;
651             logger.error(msg);
652             throw new DocumentNotFoundException(msg);
653         }
654         return entityFound;
655     }
656
657     @Override
658     public void get(ServiceContext ctx, DocumentHandler handler)
659             throws DocumentNotFoundException, DocumentException {
660         throw new UnsupportedOperationException();
661     }
662 }