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