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