1 //----------------------------------------------------------------------
2 //
3 // PerfectJPattern: "Design patterns are good but components are better!"
4 // SpringGenericDao.java Copyright (c) 2009 Giovanni Azua Garcia
5 // bravegag@hotmail.com
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 3
10 // of the License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, see <http://www.gnu.org/licenses/>.
19 //
20 //----------------------------------------------------------------------
21 package org.perfectjpattern.jee.integration.dao;
22
23 import java.io.*;
24 import java.lang.annotation.*;
25 import java.lang.reflect.*;
26 import java.util.*;
27
28 import org.hibernate.*;
29 import org.hibernate.type.Type;
30 import org.perfectjpattern.jee.api.integration.dao.*;
31
32 /**
33 * Spring-based implementation of {@link IGenericDao}
34 *
35 * @param <Id> Identification type
36 * @param <Element> Element type
37 *
38 * @author <a href="mailto:bravegag@hotmail.com">Giovanni Azua</a>
39 * @version $ $Date: Nov 5, 2008 11:40:00 AM $
40 */
41 public
42 class SpringGenericDao<Id extends Serializable, Element>
43 extends HibernateGenericDao<Id, Element>
44 implements IFinderExecutor<Element>
45 {
46 //------------------------------------------------------------------------
47 // public
48 //------------------------------------------------------------------------
49 /**
50 * {@inheritDoc}
51 */
52 @SuppressWarnings("unchecked")
53 public List<Element>
54 execute(Method aMethod, Object... anArguments)
55 {
56 final Query myQuery = prepareQuery(aMethod, anArguments);
57
58 return myQuery.list();
59 }
60
61 //------------------------------------------------------------------------
62 /**
63 * Returns the namingStrategy
64 *
65 * @return the namingStrategy
66 */
67 public final IFinderNamingStrategy
68 getNamingStrategy()
69 {
70 return theNamingStrategy;
71 }
72
73 //------------------------------------------------------------------------
74 /**
75 * Sets the namingStrategy
76 *
77 * @param aNamingStrategy the namingStrategy to set
78 */
79 public final void
80 setNamingStrategy(IFinderNamingStrategy aNamingStrategy)
81 {
82 theNamingStrategy = aNamingStrategy;
83 }
84
85 //------------------------------------------------------------------------
86 /**
87 * Returns the argumentTypeResolver
88 *
89 * @return the argumentTypeResolver
90 */
91 public final IArgumentTypeResolver
92 getArgumentTypeResolver()
93 {
94 return theArgumentTypeResolver;
95 }
96
97 //------------------------------------------------------------------------
98 /**
99 * Sets the argumentTypeResolver
100 *
101 * @param anArgumentTypeResolver the argumentTypeResolver to set
102 */
103 public final void
104 setArgumentTypeResolver(IArgumentTypeResolver anArgumentTypeResolver)
105 {
106 theArgumentTypeResolver = anArgumentTypeResolver;
107 }
108
109 //------------------------------------------------------------------------
110 // protected
111 //------------------------------------------------------------------------
112 /**
113 * Constructs a {@link SpringGenericReadOnlyDao} instance from the
114 * persistent class type and the {@link ISessionStrategy} that creates
115 * {@link Session} instances and the {@link ITransactionStrategy} that
116 * creates {@link Transaction} instances
117 *
118 * @param aPersistentClass The persistent Java Bean class
119 * @param aSessionStrategy Strategy that creates Sessions
120 * @param aTransactionStrategy Strategy that creates Transaction
121 * @throws IllegalArgumentException 'aPersistentClass' must not be null
122 * @throws IllegalArgumentException 'aPersistentClass' must be a class type
123 * @throws IllegalArgumentException 'aDaoSessionStrategy' must not be null
124 * @throws IllegalArgumentException 'aDaoTransactionStrategy' must not be
125 * null
126 */
127 protected
128 SpringGenericDao(Class<Element> aPersistentClass,
129 ISessionStrategy aSessionStrategy,
130 ITransactionStrategy aTransactionStrategy)
131 {
132 super(aPersistentClass, aSessionStrategy, aTransactionStrategy);
133 }
134
135 //------------------------------------------------------------------------
136 // private
137 //------------------------------------------------------------------------
138 private Query
139 prepareQuery(Method aMethod, Object... anArguments)
140 {
141 assert aMethod != null : "'aMethod' must not be null";
142 assert anArguments != null : "'anArguments' must not be null";
143
144 final String myQueryName = getNamingStrategy().getQueryName(
145 getPersistentClass(), aMethod);
146
147 final Query myQuery = getActualSession().getNamedQuery(myQueryName);
148
149 String[] myQueryParameters = myQuery.getNamedParameters();
150 if (myQueryParameters.length == 0)
151 {
152 setPositionalParams(myQuery, anArguments);
153 }
154 else
155 {
156 Annotation myAnnotation = null;
157 final int myNumberOfAnnotations = aMethod.getAnnotations().length;
158 for (int i = 0; i < myNumberOfAnnotations; ++i)
159 {
160 if (aMethod.getAnnotations()[i] instanceof QueryParameters)
161 {
162 myAnnotation = aMethod.getAnnotations()[i];
163
164 break;
165 }
166 }
167
168 assert myAnnotation != null : "'" + aMethod.getName() +
169 "' does not include the required QueryParameters annotation";
170
171 String[] myAnnotatedParameters = ((QueryParameters)
172 myAnnotation).names();
173
174 final int myNumberOfAnnotated = myAnnotatedParameters.length;
175 final int myNumberOfParameters = myQueryParameters.length;
176
177 assert myNumberOfAnnotated == myNumberOfParameters : "Arguments " +
178 "mismatch, all query parameters must be annotated";
179
180
181 setNamedParams(myQuery, anArguments, myQueryParameters,
182 myAnnotatedParameters);
183 }
184
185 return myQuery;
186 }
187
188 //------------------------------------------------------------------------
189 /**
190 * Assigns the parameters according to their order in the finder method
191 * signature. This method is called when the query parameters are defined
192 * as ? and not named.
193 *
194 * @param aNamedQuery The Hibernate named query defined in the mapping
195 * @param aMethodArguments The finder method argument values
196 */
197 private void
198 setPositionalParams(Query aNamedQuery, Object[] aMethodArguments)
199 {
200 assert aNamedQuery != null : "'aNamedQuery' must not be null";
201 assert aMethodArguments != null : "'anArguments' must not be null";
202
203 // set parameter
204 for (int i = 0; i < aMethodArguments.length; i++)
205 {
206 Object myArgument = aMethodArguments[i];
207
208 Type myArgumentType = getArgumentTypeResolver().getArgumentType(
209 myArgument);
210
211 if (myArgumentType != null)
212 {
213 aNamedQuery.setParameter(i, myArgument, myArgumentType);
214 }
215 else
216 {
217 aNamedQuery.setParameter(i, myArgument);
218 }
219 }
220 }
221
222 //------------------------------------------------------------------------
223 /**
224 * Assigns the parameters of the interface finder method to the named
225 * query. The parameters are assigned based on the name of the finder
226 * method arguments to match those of the named query. The arguments
227 * are not assumed to be in any specific order but will be matched
228 * according to the name. In case that there is a mismatch i.e. missing
229 * or outstanding parameter in either side a {@link IllegalAccessError}
230 * will be thrown.
231 *
232 * @param aNamedQuery The Hibernate named query defined in the mapping
233 * @param aMethodArguments The finder method argument values
234 * @param aQueryParameters The named query parameter place holders
235 * @param aMethodParameters The ordered parameter names as defined in
236 * the finder method signature
237 * @throws IllegalAccessError "Mismatching number of arguments"
238 */
239 private void
240 setNamedParams(Query aNamedQuery, Object[] aMethodArguments,
241 String[] aQueryParameters, String[] aMethodParameters)
242 {
243 assert aNamedQuery != null : "'aNamedQuery' must not be null";
244 assert aMethodArguments != null : "'anArguments' must not be null";
245 assert aQueryParameters != null : "'aNamedParameters' must not be null";
246 assert aMethodParameters != null :
247 "'anOrderedArgumentNames' must not be null";
248
249 if (aMethodArguments.length != aQueryParameters.length ||
250 aQueryParameters.length != aMethodParameters.length)
251 {
252 throw new IllegalAccessError("Mismatching number of arguments");
253 }
254
255 // populate a Map of Method parameter values keyed by parameter name
256 Map<String, Object> myArgumentsMap = new HashMap<String, Object>();
257 final int myNumberOfParameters = aMethodArguments.length;
258 for (int i = 0; i < myNumberOfParameters; i++)
259 {
260 myArgumentsMap.put(aMethodParameters[i], aMethodArguments[i]);
261 }
262
263 // finally assign the parameters
264 for (String myQueryParameter : aQueryParameters)
265 {
266 // do the parameter matching taking into account naming conventions
267 Object myArgument = null;
268 if (myArgumentsMap.containsKey(myQueryParameter))
269 {
270 myArgument = myArgumentsMap.get(myQueryParameter);
271 }
272
273 assert myArgument != null : "Query parameter '" +
274 myQueryParameter + "' was not found, check for misspelling";
275
276 Type myArgumentType = getArgumentTypeResolver().getArgumentType(
277 myArgument);
278
279 if (myArgumentType != null)
280 {
281 aNamedQuery.setParameter(myQueryParameter, myArgument,
282 myArgumentType);
283 }
284 else
285 {
286 if (myArgument instanceof Collection)
287 {
288 aNamedQuery.setParameterList(myQueryParameter,
289 (Collection<?>) myArgument);
290 }
291 else
292 {
293 aNamedQuery.setParameter(myQueryParameter, myArgument);
294 }
295 }
296 }
297 }
298
299 //------------------------------------------------------------------------
300 // members
301 //------------------------------------------------------------------------
302 /**
303 * Defines an strategy for discovering the finder methods
304 */
305 private IFinderNamingStrategy theNamingStrategy =
306 new SimpleFinderNamingStrategy();
307
308 /**
309 * Provides ability to map instances to their corresponding type
310 */
311 private IArgumentTypeResolver theArgumentTypeResolver =
312 new SimpleArgumentTypeResolver();
313 }