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