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