]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
2e25788becea69538d9e296e84aa5fd753d552fd
[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.util.Date;
21 import java.util.List;
22
23 import javax.persistence.RollbackException;
24
25 import java.sql.BatchUpdateException;
26
27 import javax.persistence.EntityExistsException;
28 import javax.persistence.EntityManager;
29 import javax.persistence.EntityManagerFactory;
30 import javax.persistence.Query;
31
32 import org.collectionspace.services.common.document.BadRequestException;
33 import org.collectionspace.services.common.document.DocumentException;
34 import org.collectionspace.services.common.document.DocumentFilter;
35 import org.collectionspace.services.common.document.DocumentHandler;
36 import org.collectionspace.services.common.document.DocumentNotFoundException;
37 import org.collectionspace.services.common.document.DocumentHandler.Action;
38 import org.collectionspace.services.common.document.DocumentWrapper;
39 import org.collectionspace.services.common.document.DocumentWrapperImpl;
40 import org.collectionspace.services.common.document.JaxbUtils;
41 import org.collectionspace.services.common.document.TransactionException;
42 import org.collectionspace.services.common.storage.StorageClient;
43 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthorityItemSpecifier;
44 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.Specifier;
45 import org.collectionspace.services.common.context.ServiceContextProperties;
46 import org.collectionspace.services.common.authorization_mgt.AuthorizationStore;
47 import org.collectionspace.services.common.context.ServiceContext;
48 import org.collectionspace.services.common.query.QueryContext;
49 import org.collectionspace.services.lifecycle.TransitionDef;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 /**
54  * JpaStorageClient is used to perform CRUD operations on SQL storage using JPA.
55  * It uses @see DocumentHandler as IOHandler with the client.
56  * All the operations in this client are carried out under their own transactions.
57  * A call to any method would start and commit/rollback a transaction.
58  * 
59  * Assumption: each persistent entityReceived has the following 3 attributes
60 <xs:element name="createdAt" type="xs:dateTime">
61 <xs:annotation>
62 <xs:appinfo>
63 <hj:basic>
64 <orm:column name="created_at" nullable="false"/>
65 </hj:basic>
66 </xs:appinfo>
67 </xs:annotation>
68 </xs:element>
69 <xs:element name="updatedAt" type="xs:dateTime">
70 <xs:annotation>
71 <xs:appinfo>
72 <hj:basic>
73 <orm:column name="updated_at" />
74 </hj:basic>
75 </xs:appinfo>
76 </xs:annotation>
77 </xs:element>
78 </xs:sequence>
79 <xs:attribute name="csid" type="xs:string">
80 <xs:annotation>
81 <xs:appinfo>
82 <hj:csidReceived>
83 <orm:column name="csid" length="128" nullable="false"/>
84 </hj:csidReceived>
85 </xs:appinfo>
86 </xs:annotation>
87 </xs:attribute>
88  *
89  * $LastChangedRevision: $ $LastChangedDate: $
90  */
91 public class JpaStorageClientImpl implements StorageClient {
92
93     /** The logger. */
94     private final Logger logger = LoggerFactory.getLogger(JpaStorageClientImpl.class);
95
96     /**
97      * Instantiates a new jpa storage client.
98      */
99     public JpaStorageClientImpl() {
100         //intentionally empty
101     }
102     
103     /* (non-Javadoc)
104      * @see org.collectionspace.services.common.storage.StorageClient#create(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
105      */
106     @Override
107     public String create(ServiceContext ctx,
108             DocumentHandler handler) throws BadRequestException,
109             DocumentException {
110         if (ctx == null) {
111             throw new IllegalArgumentException(
112                     "create: ctx is missing");
113         }
114         if (handler == null) {
115             throw new IllegalArgumentException(
116                     "create: handler is missing");
117         }
118         
119         boolean rollbackTransaction = false;
120         EntityManagerFactory emf = null;
121         EntityManager em = null;
122         try {
123             handler.prepare(Action.CREATE);
124             Object entity = handler.getCommonPart();
125             DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entity);
126             
127             emf = JpaStorageUtils.getEntityManagerFactory();            
128             em = emf.createEntityManager();
129             em.getTransaction().begin(); { //begin of transaction block            
130                     ctx.setProperty(AuthorizationStore.ENTITY_MANAGER_PROP_KEY, em);
131                     try {
132                         handler.handle(Action.CREATE, wrapDoc);
133                             JaxbUtils.setValue(entity, "setCreatedAtItem", Date.class, new Date());
134                             em.persist(entity);                 
135                     } catch (EntityExistsException ee) {
136                         //
137                         // We found an existing matching entity in the store, so we don't need to create one.  Just update the transient 'entity' instance with the existing persisted entity we found.
138                         // An entity's document handler class will throw this exception only if attempting to create (but not actually creating) duplicate is ok -e.g., Permission records.
139                         //
140                         entity = wrapDoc.getWrappedObject(); // the handler should have reset the wrapped transient object with the existing persisted entity we just found.
141                     }
142             }
143             em.getTransaction().commit();
144             handler.complete(Action.CREATE, wrapDoc);
145             return (String) JaxbUtils.getValue(entity, "getCsid");
146         } catch (BadRequestException bre) {
147                 rollbackTransaction = true;
148             throw bre;
149         } catch (DocumentException de) {
150                 rollbackTransaction = true;
151             throw de;
152         } catch (Exception e) {
153                 rollbackTransaction = true;
154             if (logger.isDebugEnabled()) {
155                 logger.debug("Caught exception ", e);
156             }
157             throw DocumentException.createDocumentException(e);
158         } finally {
159             ctx.setProperty(AuthorizationStore.ENTITY_MANAGER_PROP_KEY, null);
160             if (em != null) {
161                 if (rollbackTransaction == true) {
162                         if (em.getTransaction().isActive() == true) {
163                                 em.getTransaction().rollback();
164                         }
165                 }
166                 // Don't call this unless "em" is not null -hence the check above.
167                 JpaStorageUtils.releaseEntityManagerFactory(emf);
168             }
169         }
170
171     }
172
173     /* (non-Javadoc)
174      * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.util.List, org.collectionspace.services.common.document.DocumentHandler)
175      */
176     @Override
177     public void get(ServiceContext ctx, List<String> csidList, DocumentHandler handler)
178             throws DocumentNotFoundException, DocumentException {
179         throw new UnsupportedOperationException();
180     }
181
182     /* (non-Javadoc)
183      * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
184      */
185     @Override
186     public void get(ServiceContext ctx, String id, DocumentHandler handler)
187             throws DocumentNotFoundException, DocumentException {
188         if (ctx == null) {
189             throw new IllegalArgumentException(
190                     "get: ctx is missing");
191         }
192         if (handler == null) {
193             throw new IllegalArgumentException(
194                     "get: handler is missing");
195         }
196         EntityManagerFactory emf = null;
197         EntityManager em = null;
198         try {
199             handler.prepare(Action.GET);
200             Object o = null;
201             o = JpaStorageUtils.getEntity(getEntityName(ctx), id, 
202                     ctx.getTenantId());
203             if (null == o) {
204                 if (em != null && em.getTransaction().isActive()) {
205                     em.getTransaction().rollback();
206                 }
207                 String msg = "could not find entity with id=" + id;
208                 throw new DocumentNotFoundException(msg);
209             }
210             DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(o);
211             handler.handle(Action.GET, wrapDoc);
212             handler.complete(Action.GET, wrapDoc);
213         } catch (DocumentException de) {
214             throw de;
215         } catch (Exception e) {
216             if (logger.isDebugEnabled()) {
217                 logger.debug("Caught exception ", e);
218             }
219             throw new DocumentException(e);
220         } finally {
221             if (emf != null) {
222                 JpaStorageUtils.releaseEntityManagerFactory(emf);
223             }
224         }
225     }
226
227     /* (non-Javadoc)
228      * @see org.collectionspace.services.common.storage.StorageClient#getAll(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
229      */
230     @Override
231     public void getAll(ServiceContext ctx, DocumentHandler handler)
232             throws DocumentNotFoundException, DocumentException {
233         throw new UnsupportedOperationException("use getFiltered instead");
234     }
235
236     /* (non-Javadoc)
237      * @see org.collectionspace.services.common.storage.StorageClient#getFiltered(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
238      */
239     @Override
240     public void getFiltered(ServiceContext ctx, DocumentHandler handler)
241             throws DocumentNotFoundException, DocumentException {
242         QueryContext queryContext = new QueryContext(ctx, handler);
243         
244         DocumentFilter docFilter = handler.getDocumentFilter();
245         if (docFilter == null) {
246             docFilter = handler.createDocumentFilter();
247         }
248         EntityManagerFactory emf = null;
249         EntityManager em = null;
250         try {
251             handler.prepare(Action.GET_ALL);
252             StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
253             queryStrBldr.append(getEntityName(ctx));
254             queryStrBldr.append(" a");
255             
256             List<DocumentFilter.ParamBinding> params = docFilter.buildWhereForSearch(queryStrBldr);
257             emf = JpaStorageUtils.getEntityManagerFactory();
258             em = emf.createEntityManager();
259             String queryStr = queryStrBldr.toString(); //for debugging
260             Query q = em.createQuery(queryStr);
261             //bind parameters
262             for (DocumentFilter.ParamBinding p : params) {
263                 q.setParameter(p.getName(), p.getValue());
264             }
265             if (docFilter.getOffset() > 0) {
266                 q.setFirstResult(docFilter.getOffset());
267             }
268             if (docFilter.getPageSize() > 0) {
269                 q.setMaxResults(docFilter.getPageSize());
270             }
271
272             //FIXME is transaction required for get?
273             em.getTransaction().begin();
274             List list = q.getResultList();
275             em.getTransaction().commit();
276             DocumentWrapper<List> wrapDoc = new DocumentWrapperImpl<List>(list);
277             handler.handle(Action.GET_ALL, wrapDoc);
278             handler.complete(Action.GET_ALL, wrapDoc);
279         } catch (DocumentException de) {
280             throw de;
281         } catch (Exception e) {
282             if (logger.isDebugEnabled()) {
283                 logger.debug("Caught exception ", e);
284             }
285             throw new DocumentException(e);
286         } finally {
287             if (emf != null) {
288                 JpaStorageUtils.releaseEntityManagerFactory(emf);
289             }
290         }
291     }
292
293     /* (non-Javadoc)
294      * @see org.collectionspace.services.common.storage.StorageClient#update(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
295      */
296     @Override
297     public void update(ServiceContext ctx, String id, DocumentHandler handler)
298             throws BadRequestException, DocumentNotFoundException,
299             DocumentException {
300         if (ctx == null) {
301             throw new IllegalArgumentException(
302                     "update: ctx is missing");
303         }
304         if (handler == null) {
305             throw new IllegalArgumentException(
306                     "update: handler is missing");
307         }
308         EntityManagerFactory emf = null;
309         EntityManager em = null;
310         try {
311             handler.prepare(Action.UPDATE);
312             Object entityReceived = handler.getCommonPart();
313             emf = JpaStorageUtils.getEntityManagerFactory();
314             em = emf.createEntityManager();
315             em.getTransaction().begin();
316             Object entityFound = getEntity(em, id, entityReceived.getClass());
317             DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entityFound);
318             handler.handle(Action.UPDATE, wrapDoc);
319             JaxbUtils.setValue(entityFound, "setUpdatedAtItem", Date.class, new Date());
320             em.getTransaction().commit();
321             handler.complete(Action.UPDATE, wrapDoc);
322         } catch (BadRequestException bre) {
323             if (em != null && em.getTransaction().isActive()) {
324                 em.getTransaction().rollback();
325             }
326             throw bre;
327         } catch (DocumentException de) {
328             if (em != null && em.getTransaction().isActive()) {
329                 em.getTransaction().rollback();
330             }
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     /* 
345      * delete removes entity and its child entities
346      * cost: a get before delete
347      * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
348      */
349     @Override
350     public void delete(ServiceContext ctx, String id)
351             throws DocumentNotFoundException,
352             DocumentException {
353
354         if (logger.isDebugEnabled()) {
355             logger.debug("delete(ctx, id): deleting entity with id=" + id);
356         }
357
358         if (ctx == null) {
359             throw new IllegalArgumentException(
360                     "delete(ctx, id): ctx is missing");
361         }
362         EntityManagerFactory emf = null;
363         EntityManager em = null;
364         try {
365
366             emf = JpaStorageUtils.getEntityManagerFactory();
367             em = emf.createEntityManager();
368
369             em.getTransaction().begin();
370             Object entityFound = getEntity(ctx, em, id);
371             if (entityFound == null) {
372                 if (em != null && em.getTransaction().isActive()) {
373                     em.getTransaction().rollback();
374                 }
375                 String msg = "delete(ctx, id): 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             if (em != null && em.getTransaction().isActive()) {
384                 em.getTransaction().rollback();
385             }
386             throw de;
387         } catch (Exception e) {
388             if (logger.isDebugEnabled()) {
389                 logger.debug("delete(ctx, id): Caught exception ", e);
390             }
391             if (em != null && em.getTransaction().isActive()) {
392                 em.getTransaction().rollback();
393             }
394             throw new DocumentException(e);
395         } finally {
396             if (emf != null) {
397                 JpaStorageUtils.releaseEntityManagerFactory(emf);
398             }
399         }
400     }
401
402     /**
403      * deleteWhere uses the where clause to delete an entityReceived represented by the csidReceived
404      * it does not delete any child entities.
405      * @param ctx
406      * @param id
407      * @throws DocumentNotFoundException
408      * @throws DocumentException
409      */
410     public void deleteWhere(ServiceContext ctx, String id)
411             throws DocumentNotFoundException,
412             DocumentException {
413
414         if (ctx == null) {
415             throw new IllegalArgumentException(
416                     "deleteWhere(ctx, id) : ctx is missing");
417         }
418
419         if (logger.isDebugEnabled()) {
420             logger.debug("deleteWhere(ctx, id): deleting entity with id=" + id);
421         }
422         EntityManagerFactory emf = null;
423         EntityManager em = null;
424         try {
425             StringBuilder deleteStr = new StringBuilder("DELETE FROM ");
426             deleteStr.append(getEntityName(ctx));
427             deleteStr.append(" WHERE csid = :csid and tenantId = :tenantId");
428             //TODO: add tenant csidReceived
429
430             emf = JpaStorageUtils.getEntityManagerFactory();
431             em = emf.createEntityManager();
432             Query q = em.createQuery(deleteStr.toString());
433             q.setParameter("csid", id);
434             q.setParameter("tenantId", ctx.getTenantId());
435
436             int rcount = 0;
437             em.getTransaction().begin();
438             rcount = q.executeUpdate();
439             if (rcount != 1) {
440                 if (em != null && em.getTransaction().isActive()) {
441                     em.getTransaction().rollback();
442                 }
443                 String msg = "deleteWhere(ctx, id) could not find entity with id=" + id;
444                 logger.error(msg);
445                 throw new DocumentNotFoundException(msg);
446             }
447             em.getTransaction().commit();
448
449         } catch (DocumentException de) {
450             if (em != null && em.getTransaction().isActive()) {
451                 em.getTransaction().rollback();
452             }
453             throw de;
454         } catch (Exception e) {
455             if (logger.isDebugEnabled()) {
456                 logger.debug("deleteWhere(ctx, id) Caught exception ", e);
457             }
458             if (em != null && em.getTransaction().isActive()) {
459                 em.getTransaction().rollback();
460             }
461             throw new DocumentException(e);
462         } finally {
463             if (emf != null) {
464                 JpaStorageUtils.releaseEntityManagerFactory(emf);
465             }
466         }
467     }
468
469     /*
470      * delete removes entity and its child entities but calls back to given handler
471      * cost: a get before delete
472      * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
473      */
474     @Override
475     public boolean delete(ServiceContext ctx, String id, DocumentHandler handler)
476             throws DocumentNotFoundException, DocumentException {
477         boolean result = true;
478         
479         if (ctx == null) {
480             throw new IllegalArgumentException(
481                     "delete(ctx, ix, handler): ctx is missing");
482         }
483         if (handler == null) {
484             throw new IllegalArgumentException(
485                     "delete(ctx, ix, handler): handler is missing");
486         }
487         
488         EntityManagerFactory emf = null;
489         EntityManager em = null;
490         try {
491             handler.prepare(Action.DELETE);
492
493             emf = JpaStorageUtils.getEntityManagerFactory();
494             em = emf.createEntityManager();
495
496             em.getTransaction().begin();
497             Object entityFound = getEntity(ctx, em, id);
498             if (entityFound == null) {
499                 if (em != null && em.getTransaction().isActive()) {
500                     em.getTransaction().rollback();
501                 }
502                 String msg = "delete(ctx, ix, handler) could not find entity with id=" + id;
503                 logger.error(msg);
504                 throw new DocumentNotFoundException(msg);
505             }
506             DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entityFound);
507             handler.handle(Action.DELETE, wrapDoc);
508             em.remove(entityFound);
509             em.getTransaction().commit();
510             handler.complete(Action.DELETE, wrapDoc);
511         } catch (DocumentException de) {
512             if (em != null && em.getTransaction().isActive()) {
513                 em.getTransaction().rollback();
514             }
515             throw de;
516         } catch (Exception e) {
517             if (logger.isDebugEnabled()) {
518                 logger.debug("delete(ctx, ix, handler): Caught exception ", e);
519             }
520             if (em != null && em.getTransaction().isActive()) {
521                 em.getTransaction().rollback();
522             }
523             throw new DocumentException(e);
524         } finally {
525             if (emf != null) {
526                 JpaStorageUtils.releaseEntityManagerFactory(emf);
527             }
528         }
529         
530         return result;
531     }
532
533     /**
534      * Gets the entityReceived name.
535      * 
536      * @param ctx the ctx
537      * 
538      * @return the entityReceived name
539      */
540     protected String getEntityName(ServiceContext ctx) {
541         Object o = ctx.getProperty(ServiceContextProperties.ENTITY_NAME);
542         if (o == null) {
543             throw new IllegalArgumentException(ServiceContextProperties.ENTITY_NAME
544                     + "property is missing in context "
545                     + ctx.toString());
546         }
547
548         return (String) o;
549     }
550
551     /**
552      * getEntity returns persistent entity for given id. it assumes that
553      * service context has property ServiceContextProperties.ENTITY_CLASS set
554      * rolls back the transaction if not found
555      * @param ctx service context
556      * @param em entity manager
557      * @param csid received
558      * @return
559      * @throws DocumentNotFoundException and rollsback the transaction if active
560      */
561     protected Object getEntity(ServiceContext ctx, EntityManager em, String id)
562             throws DocumentNotFoundException {
563         Class entityClazz = (Class) ctx.getProperty(ServiceContextProperties.ENTITY_CLASS);
564         if (entityClazz == null) {
565             String msg = ServiceContextProperties.ENTITY_CLASS
566                     + " property is missing in the context";
567             logger.error(msg);
568             throw new IllegalArgumentException(msg);
569         }
570         return getEntity(em, id, entityClazz);
571     }
572
573     /**
574      * getEntity retrieves the persistent entity of given class for given id
575      * rolls back the transaction if not found
576      * @param em
577      * @param id entity id
578      * @param entityClazz
579      * @return
580      * @throws DocumentNotFoundException and rollsback the transaction if active
581      */
582     protected Object getEntity(EntityManager em, String id, Class entityClazz)
583             throws DocumentNotFoundException {
584         Object entityFound = JpaStorageUtils.getEntity(em, id, entityClazz);
585         if (entityFound == null) {
586             if (em != null && em.getTransaction().isActive()) {
587                 em.getTransaction().rollback();
588             }
589             String msg = "could not find entity of type=" + entityClazz.getName()
590                     + " with id=" + id;
591             logger.error(msg);
592             throw new DocumentNotFoundException(msg);
593         }
594         return entityFound;
595     }
596
597     @Override
598     public void get(ServiceContext ctx, DocumentHandler handler)
599             throws DocumentNotFoundException, DocumentException {
600         throw new UnsupportedOperationException();
601     }
602
603         @Override
604         public void doWorkflowTransition(ServiceContext ctx, String id,
605                         DocumentHandler handler, TransitionDef transitionDef)
606                         throws BadRequestException, DocumentNotFoundException,
607                         DocumentException {
608                 // Do nothing.  JPA services do not support workflow.
609         }
610
611         @Override
612         public void deleteWithWhereClause(ServiceContext ctx, String whereClause,
613                         DocumentHandler handler) throws DocumentNotFoundException,
614                         DocumentException {
615         throw new UnsupportedOperationException();
616         }
617
618         @Override
619         public boolean synchronize(ServiceContext ctx, Object specifier,
620                         DocumentHandler handler) throws DocumentNotFoundException,
621                         TransactionException, DocumentException {
622                 // TODO Auto-generated method stub
623                 // Do nothing. Subclasses can override if they want/need to.
624                 return true;
625         }
626         
627         @Override
628         public boolean synchronizeItem(ServiceContext ctx, AuthorityItemSpecifier itemSpecifier,
629                         DocumentHandler handler) throws DocumentNotFoundException,
630                         TransactionException, DocumentException {
631                 // TODO Auto-generated method stub
632                 // Do nothing. Subclasses can override if they want/need to.
633                 return true;
634         }
635
636 }