]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
334ce0650666ed3fc184b90f4f193030b551852c
[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                         //
139                         entity = wrapDoc.getWrappedObject(); // the handler should have reset the wrapped transient object with the existing persisted entity we just found.
140                     }
141             }
142             em.getTransaction().commit();
143             handler.complete(Action.CREATE, wrapDoc);
144             return (String) JaxbUtils.getValue(entity, "getCsid");
145         } catch (BadRequestException bre) {
146                 rollbackTransaction = true;
147             throw bre;
148         } catch (DocumentException de) {
149                 rollbackTransaction = true;
150             throw de;
151         } catch (Exception e) {
152                 rollbackTransaction = true;
153             if (logger.isDebugEnabled()) {
154                 logger.debug("Caught exception ", e);
155             }
156             throw DocumentException.createDocumentException(e);
157         } finally {
158             ctx.setProperty(AuthorizationStore.ENTITY_MANAGER_PROP_KEY, null);
159             if (em != null) {
160                 if (rollbackTransaction == true) {
161                         if (em.getTransaction().isActive() == true) {
162                                 em.getTransaction().rollback();
163                         }
164                 }
165                 // Don't call this unless "em" is not null -hence the check above.
166                 JpaStorageUtils.releaseEntityManagerFactory(emf);
167             }
168         }
169
170     }
171
172     /* (non-Javadoc)
173      * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.util.List, org.collectionspace.services.common.document.DocumentHandler)
174      */
175     @Override
176     public void get(ServiceContext ctx, List<String> csidList, DocumentHandler handler)
177             throws DocumentNotFoundException, DocumentException {
178         throw new UnsupportedOperationException();
179     }
180
181     /* (non-Javadoc)
182      * @see org.collectionspace.services.common.storage.StorageClient#get(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
183      */
184     @Override
185     public void get(ServiceContext ctx, String id, DocumentHandler handler)
186             throws DocumentNotFoundException, DocumentException {
187         if (ctx == null) {
188             throw new IllegalArgumentException(
189                     "get: ctx is missing");
190         }
191         if (handler == null) {
192             throw new IllegalArgumentException(
193                     "get: handler is missing");
194         }
195         EntityManagerFactory emf = null;
196         EntityManager em = null;
197         try {
198             handler.prepare(Action.GET);
199             Object o = null;
200             o = JpaStorageUtils.getEntity(getEntityName(ctx), id, 
201                     ctx.getTenantId());
202             if (null == o) {
203                 if (em != null && em.getTransaction().isActive()) {
204                     em.getTransaction().rollback();
205                 }
206                 String msg = "could not find entity with id=" + id;
207                 throw new DocumentNotFoundException(msg);
208             }
209             DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(o);
210             handler.handle(Action.GET, wrapDoc);
211             handler.complete(Action.GET, wrapDoc);
212         } catch (DocumentException de) {
213             throw de;
214         } catch (Exception e) {
215             if (logger.isDebugEnabled()) {
216                 logger.debug("Caught exception ", e);
217             }
218             throw new DocumentException(e);
219         } finally {
220             if (emf != null) {
221                 JpaStorageUtils.releaseEntityManagerFactory(emf);
222             }
223         }
224     }
225
226     /* (non-Javadoc)
227      * @see org.collectionspace.services.common.storage.StorageClient#getAll(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
228      */
229     @Override
230     public void getAll(ServiceContext ctx, DocumentHandler handler)
231             throws DocumentNotFoundException, DocumentException {
232         throw new UnsupportedOperationException("use getFiltered instead");
233     }
234
235     /* (non-Javadoc)
236      * @see org.collectionspace.services.common.storage.StorageClient#getFiltered(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.document.DocumentHandler)
237      */
238     @Override
239     public void getFiltered(ServiceContext ctx, DocumentHandler handler)
240             throws DocumentNotFoundException, DocumentException {
241         QueryContext queryContext = new QueryContext(ctx, handler);
242         
243         DocumentFilter docFilter = handler.getDocumentFilter();
244         if (docFilter == null) {
245             docFilter = handler.createDocumentFilter();
246         }
247         EntityManagerFactory emf = null;
248         EntityManager em = null;
249         try {
250             handler.prepare(Action.GET_ALL);
251             StringBuilder queryStrBldr = new StringBuilder("SELECT a FROM ");
252             queryStrBldr.append(getEntityName(ctx));
253             queryStrBldr.append(" a");
254             
255             List<DocumentFilter.ParamBinding> params = docFilter.buildWhereForSearch(queryStrBldr);
256             emf = JpaStorageUtils.getEntityManagerFactory();
257             em = emf.createEntityManager();
258             String queryStr = queryStrBldr.toString(); //for debugging
259             Query q = em.createQuery(queryStr);
260             //bind parameters
261             for (DocumentFilter.ParamBinding p : params) {
262                 q.setParameter(p.getName(), p.getValue());
263             }
264             if (docFilter.getOffset() > 0) {
265                 q.setFirstResult(docFilter.getOffset());
266             }
267             if (docFilter.getPageSize() > 0) {
268                 q.setMaxResults(docFilter.getPageSize());
269             }
270
271             //FIXME is transaction required for get?
272             em.getTransaction().begin();
273             List list = q.getResultList();
274             em.getTransaction().commit();
275             DocumentWrapper<List> wrapDoc = new DocumentWrapperImpl<List>(list);
276             handler.handle(Action.GET_ALL, wrapDoc);
277             handler.complete(Action.GET_ALL, wrapDoc);
278         } catch (DocumentException de) {
279             throw de;
280         } catch (Exception e) {
281             if (logger.isDebugEnabled()) {
282                 logger.debug("Caught exception ", e);
283             }
284             throw new DocumentException(e);
285         } finally {
286             if (emf != null) {
287                 JpaStorageUtils.releaseEntityManagerFactory(emf);
288             }
289         }
290     }
291
292     /* (non-Javadoc)
293      * @see org.collectionspace.services.common.storage.StorageClient#update(org.collectionspace.services.common.context.ServiceContext, java.lang.String, org.collectionspace.services.common.document.DocumentHandler)
294      */
295     @Override
296     public void update(ServiceContext ctx, String id, DocumentHandler handler)
297             throws BadRequestException, DocumentNotFoundException,
298             DocumentException {
299         if (ctx == null) {
300             throw new IllegalArgumentException(
301                     "update: ctx is missing");
302         }
303         if (handler == null) {
304             throw new IllegalArgumentException(
305                     "update: handler is missing");
306         }
307         EntityManagerFactory emf = null;
308         EntityManager em = null;
309         try {
310             handler.prepare(Action.UPDATE);
311             Object entityReceived = handler.getCommonPart();
312             emf = JpaStorageUtils.getEntityManagerFactory();
313             em = emf.createEntityManager();
314             em.getTransaction().begin();
315             Object entityFound = getEntity(em, id, entityReceived.getClass());
316             DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entityFound);
317             handler.handle(Action.UPDATE, wrapDoc);
318             JaxbUtils.setValue(entityFound, "setUpdatedAtItem", Date.class, new Date());
319             em.getTransaction().commit();
320             handler.complete(Action.UPDATE, wrapDoc);
321         } catch (BadRequestException bre) {
322             if (em != null && em.getTransaction().isActive()) {
323                 em.getTransaction().rollback();
324             }
325             throw bre;
326         } catch (DocumentException de) {
327             if (em != null && em.getTransaction().isActive()) {
328                 em.getTransaction().rollback();
329             }
330             throw de;
331         } catch (Exception e) {
332             if (logger.isDebugEnabled()) {
333                 logger.debug("Caught exception ", e);
334             }
335             throw new DocumentException(e);
336         } finally {
337             if (emf != null) {
338                 JpaStorageUtils.releaseEntityManagerFactory(emf);
339             }
340         }
341     }
342
343     /* 
344      * delete removes entity and its child entities
345      * cost: a get before delete
346      * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
347      */
348     @Override
349     public void delete(ServiceContext ctx, String id)
350             throws DocumentNotFoundException,
351             DocumentException {
352
353         if (logger.isDebugEnabled()) {
354             logger.debug("delete(ctx, id): deleting entity with id=" + id);
355         }
356
357         if (ctx == null) {
358             throw new IllegalArgumentException(
359                     "delete(ctx, id): ctx is missing");
360         }
361         EntityManagerFactory emf = null;
362         EntityManager em = null;
363         try {
364
365             emf = JpaStorageUtils.getEntityManagerFactory();
366             em = emf.createEntityManager();
367
368             em.getTransaction().begin();
369             Object entityFound = getEntity(ctx, em, id);
370             if (entityFound == null) {
371                 if (em != null && em.getTransaction().isActive()) {
372                     em.getTransaction().rollback();
373                 }
374                 String msg = "delete(ctx, id): could not find entity with id=" + id;
375                 logger.error(msg);
376                 throw new DocumentNotFoundException(msg);
377             }
378             em.remove(entityFound);
379             em.getTransaction().commit();
380
381         } catch (DocumentException de) {
382             if (em != null && em.getTransaction().isActive()) {
383                 em.getTransaction().rollback();
384             }
385             throw de;
386         } catch (Exception e) {
387             if (logger.isDebugEnabled()) {
388                 logger.debug("delete(ctx, id): Caught exception ", e);
389             }
390             if (em != null && em.getTransaction().isActive()) {
391                 em.getTransaction().rollback();
392             }
393             throw new DocumentException(e);
394         } finally {
395             if (emf != null) {
396                 JpaStorageUtils.releaseEntityManagerFactory(emf);
397             }
398         }
399     }
400
401     /**
402      * deleteWhere uses the where clause to delete an entityReceived represented by the csidReceived
403      * it does not delete any child entities.
404      * @param ctx
405      * @param id
406      * @throws DocumentNotFoundException
407      * @throws DocumentException
408      */
409     public void deleteWhere(ServiceContext ctx, String id)
410             throws DocumentNotFoundException,
411             DocumentException {
412
413         if (ctx == null) {
414             throw new IllegalArgumentException(
415                     "deleteWhere(ctx, id) : ctx is missing");
416         }
417
418         if (logger.isDebugEnabled()) {
419             logger.debug("deleteWhere(ctx, id): deleting entity with id=" + id);
420         }
421         EntityManagerFactory emf = null;
422         EntityManager em = null;
423         try {
424             StringBuilder deleteStr = new StringBuilder("DELETE FROM ");
425             deleteStr.append(getEntityName(ctx));
426             deleteStr.append(" WHERE csid = :csid and tenantId = :tenantId");
427             //TODO: add tenant csidReceived
428
429             emf = JpaStorageUtils.getEntityManagerFactory();
430             em = emf.createEntityManager();
431             Query q = em.createQuery(deleteStr.toString());
432             q.setParameter("csid", id);
433             q.setParameter("tenantId", ctx.getTenantId());
434
435             int rcount = 0;
436             em.getTransaction().begin();
437             rcount = q.executeUpdate();
438             if (rcount != 1) {
439                 if (em != null && em.getTransaction().isActive()) {
440                     em.getTransaction().rollback();
441                 }
442                 String msg = "deleteWhere(ctx, id) could not find entity with id=" + id;
443                 logger.error(msg);
444                 throw new DocumentNotFoundException(msg);
445             }
446             em.getTransaction().commit();
447
448         } catch (DocumentException de) {
449             if (em != null && em.getTransaction().isActive()) {
450                 em.getTransaction().rollback();
451             }
452             throw de;
453         } catch (Exception e) {
454             if (logger.isDebugEnabled()) {
455                 logger.debug("deleteWhere(ctx, id) Caught exception ", e);
456             }
457             if (em != null && em.getTransaction().isActive()) {
458                 em.getTransaction().rollback();
459             }
460             throw new DocumentException(e);
461         } finally {
462             if (emf != null) {
463                 JpaStorageUtils.releaseEntityManagerFactory(emf);
464             }
465         }
466     }
467
468     /*
469      * delete removes entity and its child entities but calls back to given handler
470      * cost: a get before delete
471      * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
472      */
473     @Override
474     public boolean delete(ServiceContext ctx, String id, DocumentHandler handler)
475             throws DocumentNotFoundException, DocumentException {
476         boolean result = true;
477         
478         if (ctx == null) {
479             throw new IllegalArgumentException(
480                     "delete(ctx, ix, handler): ctx is missing");
481         }
482         if (handler == null) {
483             throw new IllegalArgumentException(
484                     "delete(ctx, ix, handler): handler is missing");
485         }
486         
487         EntityManagerFactory emf = null;
488         EntityManager em = null;
489         try {
490             handler.prepare(Action.DELETE);
491
492             emf = JpaStorageUtils.getEntityManagerFactory();
493             em = emf.createEntityManager();
494
495             em.getTransaction().begin();
496             Object entityFound = getEntity(ctx, em, id);
497             if (entityFound == null) {
498                 if (em != null && em.getTransaction().isActive()) {
499                     em.getTransaction().rollback();
500                 }
501                 String msg = "delete(ctx, ix, handler) could not find entity with id=" + id;
502                 logger.error(msg);
503                 throw new DocumentNotFoundException(msg);
504             }
505             DocumentWrapper<Object> wrapDoc = new DocumentWrapperImpl<Object>(entityFound);
506             handler.handle(Action.DELETE, wrapDoc);
507             em.remove(entityFound);
508             em.getTransaction().commit();
509             handler.complete(Action.DELETE, wrapDoc);
510         } catch (DocumentException de) {
511             if (em != null && em.getTransaction().isActive()) {
512                 em.getTransaction().rollback();
513             }
514             throw de;
515         } catch (Exception e) {
516             if (logger.isDebugEnabled()) {
517                 logger.debug("delete(ctx, ix, handler): Caught exception ", e);
518             }
519             if (em != null && em.getTransaction().isActive()) {
520                 em.getTransaction().rollback();
521             }
522             throw new DocumentException(e);
523         } finally {
524             if (emf != null) {
525                 JpaStorageUtils.releaseEntityManagerFactory(emf);
526             }
527         }
528         
529         return result;
530     }
531
532     /**
533      * Gets the entityReceived name.
534      * 
535      * @param ctx the ctx
536      * 
537      * @return the entityReceived name
538      */
539     protected String getEntityName(ServiceContext ctx) {
540         Object o = ctx.getProperty(ServiceContextProperties.ENTITY_NAME);
541         if (o == null) {
542             throw new IllegalArgumentException(ServiceContextProperties.ENTITY_NAME
543                     + "property is missing in context "
544                     + ctx.toString());
545         }
546
547         return (String) o;
548     }
549
550     /**
551      * getEntity returns persistent entity for given id. it assumes that
552      * service context has property ServiceContextProperties.ENTITY_CLASS set
553      * rolls back the transaction if not found
554      * @param ctx service context
555      * @param em entity manager
556      * @param csid received
557      * @return
558      * @throws DocumentNotFoundException and rollsback the transaction if active
559      */
560     protected Object getEntity(ServiceContext ctx, EntityManager em, String id)
561             throws DocumentNotFoundException {
562         Class entityClazz = (Class) ctx.getProperty(ServiceContextProperties.ENTITY_CLASS);
563         if (entityClazz == null) {
564             String msg = ServiceContextProperties.ENTITY_CLASS
565                     + " property is missing in the context";
566             logger.error(msg);
567             throw new IllegalArgumentException(msg);
568         }
569         return getEntity(em, id, entityClazz);
570     }
571
572     /**
573      * getEntity retrieves the persistent entity of given class for given id
574      * rolls back the transaction if not found
575      * @param em
576      * @param id entity id
577      * @param entityClazz
578      * @return
579      * @throws DocumentNotFoundException and rollsback the transaction if active
580      */
581     protected Object getEntity(EntityManager em, String id, Class entityClazz)
582             throws DocumentNotFoundException {
583         Object entityFound = JpaStorageUtils.getEntity(em, id, entityClazz);
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
602         @Override
603         public void doWorkflowTransition(ServiceContext ctx, String id,
604                         DocumentHandler handler, TransitionDef transitionDef)
605                         throws BadRequestException, DocumentNotFoundException,
606                         DocumentException {
607                 // Do nothing.  JPA services do not support workflow.
608         }
609
610         @Override
611         public void deleteWithWhereClause(ServiceContext ctx, String whereClause,
612                         DocumentHandler handler) throws DocumentNotFoundException,
613                         DocumentException {
614         throw new UnsupportedOperationException();
615         }
616
617         @Override
618         public boolean synchronize(ServiceContext ctx, Object specifier,
619                         DocumentHandler handler) throws DocumentNotFoundException,
620                         TransactionException, DocumentException {
621                 // TODO Auto-generated method stub
622                 // Do nothing. Subclasses can override if they want/need to.
623                 return true;
624         }
625         
626         @Override
627         public boolean synchronizeItem(ServiceContext ctx, AuthorityItemSpecifier itemSpecifier,
628                         DocumentHandler handler) throws DocumentNotFoundException,
629                         TransactionException, DocumentException {
630                 // TODO Auto-generated method stub
631                 // Do nothing. Subclasses can override if they want/need to.
632                 return true;
633         }
634
635 }