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