View Javadoc

1   /*
2    * Copyright (c) 2004-2005, University Health Network.  All rights reserved. Distributed under the BSD 
3    * license (see http://opensource.org/licenses/bsd-license.php).
4    *  
5    * HibernateQueryResultStore.java
6    *
7    * Created on 15-Dec-2004 at 1:24:03 PM
8    */
9   package ca.uhn.cache.internal.impl;
10  
11  import java.sql.SQLException;
12  import java.text.MessageFormat;
13  import java.util.HashMap;
14  import java.util.Iterator;
15  import java.util.List;
16  import java.util.Map;
17  import java.util.Set;
18  
19  import net.sf.hibernate.Hibernate;
20  import net.sf.hibernate.HibernateException;
21  import net.sf.hibernate.Query;
22  import net.sf.hibernate.Session;
23  
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  import org.springframework.beans.BeanUtils;
27  import org.springframework.beans.factory.InitializingBean;
28  import org.springframework.dao.DataAccessException;
29  import org.springframework.orm.hibernate.HibernateCallback;
30  import org.springframework.orm.hibernate.support.HibernateDaoSupport;
31  
32  import ca.uhn.cache.IDataItem;
33  import ca.uhn.cache.IDimension;
34  import ca.uhn.cache.IGroupQueryParam;
35  import ca.uhn.cache.IParamSpace;
36  import ca.uhn.cache.IPointQueryParam;
37  import ca.uhn.cache.IQuery;
38  import ca.uhn.cache.IQueryParam;
39  import ca.uhn.cache.IQueryResult;
40  import ca.uhn.cache.impl.DataItem;
41  import ca.uhn.cache.impl.DateParam;
42  import ca.uhn.cache.impl.DateRangeParam;
43  import ca.uhn.cache.impl.QueryResult;
44  import ca.uhn.cache.impl.StringParam;
45  import ca.uhn.cache.impl.StringSetParam;
46  import ca.uhn.cache.internal.IQueryResultStore;
47  import ca.uhn.cache.internal.exception.QueryResultStoreException;
48  import ca.uhn.cache.internal.hibernate.IQueryParamHelper;
49  import ca.uhn.cache.internal.hibernate.exception.HibernateQueryResultStoreException;
50  import ca.uhn.cache.internal.hibernate.impl.DateParamHelper;
51  import ca.uhn.cache.internal.hibernate.impl.Field;
52  import ca.uhn.cache.internal.hibernate.impl.Record;
53  import ca.uhn.cache.internal.hibernate.impl.StringParamHelper;
54  import ca.uhn.cache.internal.util.TimestampUtils;
55  
56  
57  /***
58   * <b>Hibernate</b> implementation of <code>IQueryResultStore</code>.
59   * 
60   * @author <a href="mailto:alexei.guevara@uhn.on.ca">Alexei Guevara</a>
61   * @version $Revision: 1.1 $ updated on $Date: 2005/01/24 22:54:03 $ by $Author: bryan_tripp $
62   */
63  public class HibernateQueryResultStore extends HibernateDaoSupport implements IQueryResultStore, InitializingBean {
64      
65      private static final Log ourLog = LogFactory.getLog( HibernateQueryResultStore.class );
66      private final Map myDimensionNameToDimensionMap;
67      
68      private IParamSpace myParamSpace;
69      
70      private final Map myQueryParamToFieldMap;
71  
72      /***
73       */
74      public HibernateQueryResultStore() {
75          myQueryParamToFieldMap = new HashMap();
76          myDimensionNameToDimensionMap = new HashMap();
77          
78          registerQueryParamToQueryParamHelper( StringParam.class, new StringParamHelper() ); 
79          registerQueryParamToQueryParamHelper( DateParam.class, new DateParamHelper() );
80          registerQueryParamToQueryParamHelper( StringSetParam.class, new StringParamHelper() ); 
81          registerQueryParamToQueryParamHelper( DateRangeParam.class, new DateParamHelper() );
82          
83      }
84  
85      /***
86       * {@inheritDoc}
87       */
88      public int delete( IQuery theProjection ) throws QueryResultStoreException {
89          int retVal;
90          
91          //TODO(P2): optimize, avoid having the resolve the query before deleting the records.
92          
93          final String hql = generateSelectHql( theProjection );
94          final Map hqlParamMap = generateHqlParamMap( theProjection );
95          
96          final List recordList = getHibernateTemplate().executeFind( 
97                  new HibernateCallback() {
98                      public Object doInHibernate( Session theSession ) throws HibernateException, SQLException {
99                          Query query = theSession.createQuery( hql );
100                         
101                         Set paramNames = hqlParamMap.keySet();
102                         for (Iterator iter = paramNames.iterator(); iter.hasNext();) {
103                             String paramName = (String) iter.next();
104                             query.setParameter( paramName, hqlParamMap.get( paramName ) );
105                         }
106                         
107                         return query.list();
108                     }
109                 } );
110 
111         retVal = recordList.size();
112         
113         //delete the recordList
114         getHibernateTemplate().execute( 
115                 new HibernateCallback() {
116                     public Object doInHibernate( Session theSession ) throws HibernateException, SQLException {
117                         for (Iterator iter = recordList.iterator(); iter.hasNext();) {
118                             Record record = (Record) iter.next();
119                             theSession.delete( record ); 
120                         }
121                         return null;
122                     }
123                 } );
124 
125         return retVal;
126     }
127 
128     /***
129      * @return Returns the paramSpace.
130      */
131     public IParamSpace getParamSpace() {
132         return myParamSpace;
133     }
134     
135     /***
136      * {@inheritDoc}
137      */
138     public void insert( IQueryResult theQueryResult ) throws QueryResultStoreException {
139         for (Iterator qrIter = theQueryResult.iterator(); qrIter.hasNext();) {
140             
141             IDataItem dataItem = (IDataItem) qrIter.next();
142             
143             final Record record = createRecordFrom( dataItem );
144             
145             //all the query params in the data item projection are <code>IPointQueryParam</code>s.
146             final IQuery projection = dataItem.getProjection();
147             
148             try {
149                 //perform the insertion of the record and the fields within a transaction.
150                 getHibernateTemplate().execute(
151                         new HibernateCallback() {
152                             /***
153                              * {@inheritDoc}
154                              */
155                             public  Object doInHibernate(Session theSession) throws HibernateException {
156 
157                                 Record foundRecord = findRecordByDataItemId( theSession, record.getDataItemId() );
158                                 if ( foundRecord != null ) {
159                                     
160                                     //update the record
161                                     copy( record, foundRecord );
162                                     theSession.update( foundRecord );
163                                     
164                                     //update fields
165                                     Set foundFields = foundRecord.getFields();
166                                     Iterator foundFieldsIter = foundFields.iterator();
167                                     
168                                     Set parameters = projection.getParameters();
169                                     for (Iterator queryParamIter = parameters.iterator(); queryParamIter.hasNext();) {
170                                         IPointQueryParam pqp = (IPointQueryParam) queryParamIter.next();
171                                         
172                                         Field foundField = (Field) foundFieldsIter.next();
173 
174                                         //create up-to-date field
175                                         Field field = createFieldFrom( record, pqp );
176                                         
177                                         //copy over db field
178                                         copy( field, foundField );
179                                         
180                                         //update the field
181                                         theSession.update( foundField );
182                                     }
183                                 }
184                                 else {
185                                     //insert record
186                                     theSession.save( record );
187                                     
188                                     Set parameters = projection.getParameters();
189                                     for (Iterator queryParamIter = parameters.iterator(); queryParamIter.hasNext();) {
190                                         
191                                         IPointQueryParam pqp = (IPointQueryParam) queryParamIter.next();
192                                         
193                                         Field field = createFieldFrom( record, pqp );
194                                         //insert field
195                                         theSession.save( field );
196                                     }
197                                 }
198                                 
199                                 
200                                 return null;
201                                 
202                             }
203                         }
204                 );
205             }
206             catch (DataAccessException e) {
207                   throw new HibernateQueryResultStoreException(
208                         "Exception thrown by Hibernate while trying to insert the record" + record, e );
209             }
210         }
211     }
212 
213     /***
214      * copy all the attributes except: hibernateId and fields.
215      * 
216      * TODO use reflection
217      */
218     protected void copy( Record theSource, Record theDestination ) {
219         BeanUtils.copyProperties( theSource, theDestination, new String[] { "hibernateId" , "fields" } );
220     }
221     
222     /***
223      * copy all the attributes except: hibernateId and record.
224      * 
225      * TODO use reflection
226      */
227     protected void copy( Field theSource, Field theDestination ) {
228         BeanUtils.copyProperties( theSource, theDestination, new String[] { "hibernateId", "record"} );
229     }
230 
231     /***
232      * {@inheritDoc}
233      */
234     public IQueryResult select( IQuery theQuery ) throws QueryResultStoreException {
235         IQueryResult retVal;
236         
237         final String hql = generateSelectHql( theQuery );
238         final Map hqlParamMap = generateHqlParamMap( theQuery );
239         
240         List recordList = getHibernateTemplate().executeFind( 
241                 new HibernateCallback() {
242                     public Object doInHibernate( Session theSession ) throws HibernateException, SQLException {
243                         Query query = theSession.createQuery( hql );
244                         
245                         Set paramNames = hqlParamMap.keySet();
246                         for (Iterator iter = paramNames.iterator(); iter.hasNext();) {
247                             String paramName = (String) iter.next();
248                             query.setParameter( paramName, hqlParamMap.get( paramName ) );
249                         }
250                         
251                         return query.list();
252                     }
253                 } );        
254 
255         retVal = createQueryResultFromRecordList( recordList );
256         
257         return retVal;
258     }
259     /***
260      * @param theParamSpace The paramSpace to set.
261      */
262     public void setParamSpace( IParamSpace theParamSpace ) {
263         myParamSpace = theParamSpace;
264         
265         IDimension[] dimensions = theParamSpace.getDimensions();
266         for (int i = 0; i < dimensions.length; i++) {
267             IDimension dimension = dimensions[i];
268             myDimensionNameToDimensionMap.put( dimension.getName(), dimension );
269         }
270         
271     }
272 
273     private IDataItem createCachedDataItemFromRecord( Record theRecord ) {
274         IDataItem retVal;
275         
276         retVal = new DataItem(
277                 theRecord.getDataItemId(),
278                 theRecord.getValue(), 
279                 createProjection( theRecord ),
280                 theRecord.getVolatility(),
281                 TimestampUtils.timestampToDate(theRecord.getLastUpdate()));
282         
283         return retVal;
284     }
285     
286     private IQuery createProjection( Record theRecord ) {
287         IQuery retVal = new ca.uhn.cache.impl.Query();
288         
289         Set fields = theRecord.getFields();
290         for (Iterator iter = fields.iterator(); iter.hasNext();) {
291             Field field = (Field) iter.next();
292             IQueryParam qp = field.toQueryParam( myDimensionNameToDimensionMap );
293             retVal.addParameter( qp );
294         }
295         
296         return retVal;
297     }
298     
299     private IQueryResult createQueryResultFromRecordList( List theRecordList ) {
300         IQueryResult retVal = new QueryResult();
301         
302         for (Iterator iter = theRecordList.iterator(); iter.hasNext();) {
303             Record record = (Record) iter.next();
304             IDataItem di = createCachedDataItemFromRecord( record );
305             retVal.add( di );
306         }
307         
308         return retVal;
309     }
310     
311     private String generateSelectHql( IQuery theQuery ) {
312         String retVal;
313         
314         String fromAndWhereHql = generateFromAndWhereHql( theQuery );
315         retVal = MessageFormat.format( "SELECT f.record {0}", new Object[] { fromAndWhereHql } );
316         
317         return retVal;
318     }
319     
320     private String generateFromAndWhereHql( IQuery theQuery ) {
321         String retVal;
322         
323         Set queryParams = theQuery.getParameters();
324 
325         StringBuffer fromBuffer = new StringBuffer( "FROM " +Field.class.getName()+" f" );
326         int qpIndex = 1;
327         for (Iterator iter = queryParams.iterator(); iter.hasNext();) {
328             IQueryParam qp = (IQueryParam) iter.next();
329             IQueryParamHelper queryParamHelper = getQueryParamHelper(qp);
330             Class fieldClass = queryParamHelper.getFieldClass();
331             
332             fromBuffer.append( 
333                     MessageFormat.format( 
334                             ", {0} f{1}", 
335                             new Object[] { fieldClass.getName(), new Integer(qpIndex++)  } ) );
336             
337         }
338         
339         StringBuffer whereBuffer = new StringBuffer( "WHERE " );
340         qpIndex = 1;
341         for (Iterator iter = queryParams.iterator(); iter.hasNext();) {
342             IQueryParam qp = (IQueryParam) iter.next();
343             IQueryParamHelper queryParamHelper = getQueryParamHelper(qp);
344             
345             if ( qpIndex > 1 ) {
346                 whereBuffer.append( " AND " );
347             }
348             
349             if ( qp instanceof IPointQueryParam ) {
350                 whereBuffer.append( queryParamHelper.generateHql( (IPointQueryParam) qp, qpIndex++ ) );
351             }
352             else if ( qp instanceof IGroupQueryParam ) {
353                 whereBuffer.append( queryParamHelper.generateHql( (IGroupQueryParam) qp, qpIndex++ ) );
354             }
355             else {
356                 throw new RuntimeException( "The query parameter " +qp+
357                                         " MUST implement the interface " 
358                                         +IPointQueryParam.class.getName()+ 
359                                         " xor the interface " 
360                                         +IGroupQueryParam.class.getName());
361             }
362             
363         }
364         
365         retVal = MessageFormat.format( "{0} {1}", new Object[] {  fromBuffer, whereBuffer } );
366         
367         return retVal;
368     }
369     
370     
371     private Map generateHqlParamMap( IQuery theQuery ) {
372         Map retVal = new HashMap();
373         
374         int qpIndex = 1;
375         Set parameters = theQuery.getParameters();
376         for (Iterator iter = parameters.iterator(); iter.hasNext();) {
377             IQueryParam qp = (IQueryParam) iter.next();
378             IQueryParamHelper qpHelper = getQueryParamHelper(qp);
379             
380             if ( qp instanceof IPointQueryParam ) {
381                 retVal.putAll( qpHelper.generateHqlParamMap( (IPointQueryParam) qp, qpIndex++ ) );
382             }
383             else if ( qp instanceof IGroupQueryParam ) {
384                 retVal.putAll( qpHelper.generateHqlParamMap( (IGroupQueryParam) qp, qpIndex++ ) );
385             }
386             else {
387                 throw new RuntimeException( "The query parameter " +qp+
388                                         " MUST implement the interface " 
389                                         +IPointQueryParam.class.getName()+ 
390                                         " xor the interface " 
391                                         +IGroupQueryParam.class.getName());
392             }
393         }
394         
395         return retVal;
396     }
397     
398     /***
399      * Creates the corresponding <code>Field</code> subclass for the provided <code>IQueryParam</code>.
400      * 
401      * Use {@link #registerQueryParamToField(Class, IFieldFactory)} to register mapping between an
402      * <code>IQueryParam</code> and the corresponding <code>Field</code> subclass.
403      * 
404      * @param theRecord The record the field will be associated with.
405      * @param theQueryParam The provided <code>IPointQueryParam</code>.
406      * @return The created <code>Field</code>, or <b>null</b> if no mapping has 
407      *         been registered for the provided <code>IQueryParam</code> 
408      */
409     protected Field createFieldFrom( Record theRecord, IPointQueryParam theQueryParam ) {
410         Field retVal = null;
411         
412         IQueryParamHelper queryParamHelper = getQueryParamHelper( theQueryParam );
413         if ( queryParamHelper != null ) {
414             retVal = queryParamHelper.createField( theRecord, theQueryParam );
415         }
416         
417         return retVal; 
418     }
419 
420     /***
421      * Creates the corresponding <code>Record</code> for the provided <code>IQuery</code>.
422      * 
423      * @param theDataItem The provided <code>IQuery</code>.
424      * @return The created <code>Record</code>. 
425      */
426     protected Record createRecordFrom( IDataItem theDataItem ) {
427         Record retVal = new Record();
428         
429         retVal.setDataItemId( theDataItem.getId() );
430         retVal.setValue( theDataItem.getValue() );
431         retVal.setVolatility( theDataItem.getVolatility() );
432         
433         if ( theDataItem.getLastUpdate() != null) {
434             retVal.setLastUpdate( TimestampUtils.dateToTimestamp( theDataItem.getLastUpdate() ) );    
435         }
436         
437         return retVal;
438     }
439     
440     /***
441      * Retrieves the <code>IQueryParamHelper</code> corresponding to the provided query param.
442      * 
443      * @param theQueryParam The provided query param.
444      * @return The corresponding <code>IQueryParamHelper</code>, or <b>null</b> if there is no 
445      *         corresponding <code>IQueryParamHelper</code> for the provided query param.
446      *         
447      * @see #registerQueryParamToQueryParamHelper(Class, IQueryParamHelper)         
448      */
449     protected IQueryParamHelper getQueryParamHelper( IQueryParam theQueryParam ) {
450         IQueryParamHelper queryParamHelper = (IQueryParamHelper) myQueryParamToFieldMap.get( theQueryParam.getClass() );
451         return queryParamHelper;
452     }
453 
454     /***
455      * Registers the mapping between an <code>IQueryParam</code> and the corresponding <code>Field</code> subclass.
456      * 
457      * @param theQueryParamClazz The query param class.
458      * @param theFieldFactory The <code>Field</code> subclass factory.
459      */
460     protected void registerQueryParamToQueryParamHelper( Class theQueryParamClazz, IQueryParamHelper theFieldFactory ) {
461         myQueryParamToFieldMap.put( theQueryParamClazz, theFieldFactory );
462     }
463     
464     //// SECTIOM BEGIN: HELPER METHODS ////
465     
466     private Record findRecordByDataItemId( Session theSession, String theDataItemId ) throws HibernateException {
467         Record retVal = null;
468         
469         List recordList = 
470             theSession.find( 
471                     "FROM " +Record.class.getName()+ " WHERE dataItemId = ?" , 
472                     theDataItemId, 
473                     Hibernate.STRING );
474         
475         if ( !recordList.isEmpty() ) {
476             retVal = (Record) recordList.get( 0 );
477         }
478         return retVal;
479     }
480     
481     ////  SECTIOM END: HELPER METHODS ////
482     
483 }
484