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