1 /* Copyright 2004-2005 Graeme Rocher
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 package org.codehaus.groovy.grails.orm.hibernate.cfg;
16
17 import groovy.lang.GroovyObject;
18 import groovy.lang.GroovySystem;
19 import groovy.lang.MetaClass;
20 import org.apache.commons.beanutils.PropertyUtils;
21 import org.apache.commons.lang.StringUtils;
22 import org.apache.commons.logging.Log;
23 import org.apache.commons.logging.LogFactory;
24 import org.codehaus.groovy.grails.commons;
25 import org.codehaus.groovy.grails.orm.hibernate.GrailsHibernateDomainClass;
26 import org.codehaus.groovy.grails.orm.hibernate.proxy.GroovyAwareJavassistProxyFactory;
27 import org.hibernate;
28 import org.hibernate.criterion.Order;
29 import org.hibernate.engine.EntityEntry;
30 import org.hibernate.engine.SessionImplementor;
31 import org.hibernate.engine.Status;
32 import org.hibernate.mapping.PersistentClass;
33 import org.hibernate.mapping.Property;
34 import org.hibernate.metadata.ClassMetadata;
35 import org.hibernate.property.Getter;
36 import org.hibernate.property.Setter;
37 import org.hibernate.proxy.HibernateProxy;
38 import org.hibernate.proxy.LazyInitializer;
39 import org.hibernate.type.AbstractComponentType;
40 import org.springframework.beans.SimpleTypeConverter;
41 import org.springframework.orm.hibernate3.HibernateCallback;
42 import org.springframework.orm.hibernate3.HibernateTemplate;
43
44 import java.lang.reflect.InvocationTargetException;
45 import java.lang.reflect.Modifier;
46 import java.sql.SQLException;
47 import java.util.HashMap;
48 import java.util.HashSet;
49 import java.util.Map;
50 import java.util.Set;
51
52 /**
53 * A class containing utility methods for configuring Hibernate inside Grails
54 *
55 * @author Graeme Rocher
56 * @since 0.4
57 * <p/>
58 * Created: Jan 19, 2007
59 * Time: 6:21:01 PM
60 */
61 public class GrailsHibernateUtil {
62 private static final Log LOG = LogFactory.getLog(GrailsHibernateUtil.class);
63 public static SimpleTypeConverter converter = new SimpleTypeConverter();
64 public static final String ARGUMENT_MAX = "max";
65 public static final String ARGUMENT_OFFSET = "offset";
66 public static final String ARGUMENT_ORDER = "order";
67 public static final String ARGUMENT_SORT = "sort";
68 public static final String ORDER_DESC = "desc";
69 public static final String ORDER_ASC = "asc";
70 public static final String ARGUMENT_FETCH = "fetch";
71 public static final String ARGUMENT_IGNORE_CASE = "ignoreCase";
72 public static final String ARGUMENT_CACHE = "cache";
73 public static final String ARGUMENT_LOCK = "lock";
74 public static final String CONFIG_PROPERTY_CACHE_QUERIES="grails.hibernate.cache.queries";
75 public static final Class[] EMPTY_CLASS_ARRAY=new Class[0];
76
77
78 public static void configureHibernateDomainClasses(SessionFactory sessionFactory, GrailsApplication application) {
79 Map hibernateDomainClassMap = new HashMap();
80 for (Object o : sessionFactory.getAllClassMetadata().values()) {
81 ClassMetadata classMetadata = (ClassMetadata) o;
82 configureDomainClass(sessionFactory, application, classMetadata, classMetadata.getMappedClass(EntityMode.POJO), hibernateDomainClassMap);
83 }
84 configureInheritanceMappings(hibernateDomainClassMap);
85 }
86
87 public static void configureInheritanceMappings(Map hibernateDomainClassMap) {
88 // now get through all domainclasses, and add all subclasses to root class
89 for (Object o : hibernateDomainClassMap.values()) {
90 GrailsDomainClass baseClass = (GrailsDomainClass) o;
91 if (!baseClass.isRoot()) {
92 Class superClass = baseClass
93 .getClazz().getSuperclass();
94
95
96 while (!superClass.equals(Object.class) && !superClass.equals(GroovyObject.class)) {
97 GrailsDomainClass gdc = (GrailsDomainClass) hibernateDomainClassMap.get(superClass.getName());
98
99 if (gdc == null || gdc.getSubClasses() == null) {
100 LOG.debug("did not find superclass names when mapping inheritance....");
101 break;
102 }
103 gdc.getSubClasses().add(baseClass);
104 superClass = superClass.getSuperclass();
105 }
106 }
107 }
108 }
109
110 private static void configureDomainClass(SessionFactory sessionFactory, GrailsApplication application, ClassMetadata cmd, Class persistentClass, Map hibernateDomainClassMap) {
111 if (!Modifier.isAbstract(persistentClass.getModifiers())) {
112 LOG.trace("Configuring domain class [" + persistentClass + "]");
113 GrailsDomainClass dc = (GrailsDomainClass) application.getArtefact(DomainClassArtefactHandler.TYPE, persistentClass.getName());
114 if (dc == null) {
115 // a patch to add inheritance to this system
116 GrailsHibernateDomainClass ghdc = new
117 GrailsHibernateDomainClass(persistentClass, sessionFactory, cmd);
118
119 hibernateDomainClassMap.put(persistentClass.getName(),
120 ghdc);
121
122 dc = (GrailsDomainClass) application.addArtefact(DomainClassArtefactHandler.TYPE, ghdc);
123 }
124 }
125 }
126
127 public static void populateArgumentsForCriteria(Class targetClass, Criteria c, Map argMap) {
128 Integer maxParam = null;
129 Integer offsetParam = null;
130 if(argMap.containsKey(ARGUMENT_MAX)) {
131 maxParam = (Integer)converter.convertIfNecessary(argMap.get(ARGUMENT_MAX),Integer.class);
132 }
133 if(argMap.containsKey(ARGUMENT_OFFSET)) {
134 offsetParam = (Integer)converter.convertIfNecessary(argMap.get(ARGUMENT_OFFSET),Integer.class);
135 }
136 String orderParam = (String)argMap.get(ARGUMENT_ORDER);
137 Object fetchObj = argMap.get(ARGUMENT_FETCH);
138 if(fetchObj instanceof Map) {
139 Map fetch = (Map)fetchObj;
140 for (Object o : fetch.keySet()) {
141 String associationName = (String) o;
142 c.setFetchMode(associationName, getFetchMode(fetch.get(associationName)));
143 }
144 }
145
146 final String sort = (String)argMap.get(ARGUMENT_SORT);
147 final String order = ORDER_DESC.equalsIgnoreCase(orderParam) ? ORDER_DESC : ORDER_ASC;
148 final int max = maxParam == null ? -1 : maxParam;
149 final int offset = offsetParam == null ? -1 : offsetParam;
150 if(max > -1)
151 c.setMaxResults(max);
152 if(offset > -1)
153 c.setFirstResult(offset);
154 if(GrailsClassUtils.getBooleanFromMap(ARGUMENT_CACHE, argMap)) {
155 c.setCacheable(true);
156 }
157 if(GrailsClassUtils.getBooleanFromMap(ARGUMENT_LOCK, argMap)) {
158 c.setLockMode(LockMode.UPGRADE);
159 }
160 else {
161 if(argMap.get(ARGUMENT_CACHE) == null) {
162 cacheCriteriaByMapping(targetClass, c);
163 }
164 }
165 if(sort != null) {
166 boolean ignoreCase = true;
167 Object caseArg = argMap.get(ARGUMENT_IGNORE_CASE);
168 if(caseArg instanceof Boolean) {
169 ignoreCase = (Boolean) caseArg;
170 }
171 if(ORDER_DESC.equals(order)) {
172 c.addOrder( ignoreCase ? Order.desc(sort).ignoreCase() : Order.desc(sort));
173 }
174 else {
175 c.addOrder( ignoreCase ? Order.asc(sort).ignoreCase() : Order.asc(sort) );
176 }
177 }
178 else {
179 Mapping m = GrailsDomainBinder.getMapping(targetClass);
180 if(m!=null&&!StringUtils.isBlank(m.getSort())) {
181 if(ORDER_DESC.equalsIgnoreCase(m.getOrder())) {
182 c.addOrder(Order.desc(m.getSort()));
183 }
184 else {
185 c.addOrder(Order.asc(m.getSort()));
186 }
187 }
188 }
189
190 }
191
192 /**
193 * Configures the criteria instance to cache based on the configured mapping
194 *
195 * @param targetClass The target class
196 * @param criteria The criteria
197 */
198 public static void cacheCriteriaByMapping(Class targetClass, Criteria criteria) {
199 Mapping m = GrailsDomainBinder.getMapping(targetClass);
200 if(m!=null && m.getCache()!=null) {
201 if(m.getCache().getEnabled()) {
202 criteria.setCacheable(true);
203 }
204 }
205 }
206
207 public static void populateArgumentsForCriteria(Criteria c, Map argMap) {
208 populateArgumentsForCriteria(null,c, argMap);
209 }
210
211 /**
212 * Will retrieve the fetch mode for the specified instance other wise return the
213 * default FetchMode
214 *
215 * @param object The object, converted to a string
216 * @return The FetchMode
217 */
218 public static FetchMode getFetchMode(Object object) {
219 String name = object != null ? object.toString() : "default";
220 if(name.equalsIgnoreCase(FetchMode.JOIN.toString()) || name.equalsIgnoreCase("eager")) {
221 return FetchMode.JOIN;
222 }
223 else if(name.equalsIgnoreCase(FetchMode.SELECT.toString()) || name.equalsIgnoreCase("lazy")) {
224 return FetchMode.SELECT;
225 }
226 return FetchMode.DEFAULT;
227 }
228
229 /**
230 * Sets the target object to read-only using the given SessionFactory instance. This
231 * avoids Hibernate performing any dirty checking on the object
232 *
233 * @see #setObjectToReadWrite(Object, org.hibernate.SessionFactory)
234 *
235 * @param target The target object
236 * @param sessionFactory The SessionFactory instance
237 */
238 public static void setObjectToReadyOnly(Object target, SessionFactory sessionFactory) {
239 Session session = sessionFactory.getCurrentSession();
240 if(canModifyReadWriteState(session, target)) {
241 if(target instanceof HibernateProxy) {
242 target = ((HibernateProxy)target).getHibernateLazyInitializer().getImplementation();
243 }
244 session.setReadOnly(target, true);
245 session.setFlushMode(FlushMode.MANUAL);
246 }
247 }
248
249 private static boolean canModifyReadWriteState(Session session, Object target) {
250 return session.contains(target) && Hibernate.isInitialized(target);
251 }
252
253 /**
254 * Sets the target object to read-write, allowing Hibernate to dirty check it and auto-flush
255 * changes
256 *
257 * @see #setObjectToReadyOnly(Object, org.hibernate.SessionFactory)
258 *
259 * @param target The target object
260 * @param sessionFactory The SessionFactory instance
261 */
262 public static void setObjectToReadWrite(final Object target, SessionFactory sessionFactory) {
263 HibernateTemplate template = new HibernateTemplate(sessionFactory);
264 template.setExposeNativeSession(true);
265 template.execute(new HibernateCallback() {
266
267 public Object doInHibernate(Session session) throws HibernateException, SQLException {
268 if(canModifyReadWriteState(session, target)) {
269 SessionImplementor sessionImpl = (SessionImplementor) session;
270 EntityEntry ee = sessionImpl.getPersistenceContext().getEntry(target);
271
272 if(ee != null && ee.getStatus() == Status.READ_ONLY) {
273 Object actualTarget = target;
274 if(target instanceof HibernateProxy) {
275 actualTarget = ((HibernateProxy)target).getHibernateLazyInitializer().getImplementation();
276 }
277
278 session.setReadOnly(actualTarget, false);
279 session.setFlushMode(FlushMode.AUTO);
280 incrementVersion(target);
281 }
282 }
283 return null;
284
285 }
286 });
287
288 }
289
290 /**
291 * Increments the entities version number in order to force an update
292 * @param target The target entity
293 */
294 public static void incrementVersion(Object target) {
295 MetaClass metaClass = GroovySystem.getMetaClassRegistry().getMetaClass(target.getClass());
296 if(metaClass.hasProperty(target, GrailsDomainClassProperty.VERSION)!=null) {
297 Object version = metaClass.getProperty(target, GrailsDomainClassProperty.VERSION);
298 if(version instanceof Long) {
299 Long newVersion = (Long) version + 1;
300 metaClass.setProperty(target, GrailsDomainClassProperty.VERSION, newVersion);
301 }
302 }
303 }
304
305 /**
306 * Unwraps and initializes a HibernateProxy
307 * @param proxy The proxy
308 */
309 public static Object unwrapProxy(HibernateProxy proxy) {
310 LazyInitializer lazyInitializer = proxy.getHibernateLazyInitializer();
311 if(lazyInitializer.isUninitialized()) {
312 lazyInitializer.initialize();
313 }
314 return lazyInitializer.getImplementation();
315 }
316
317
318 /**
319 * Returns the proxy for a given association or null if it is not proxied
320 *
321 * @param obj The object
322 * @param associationName The named assoication
323 * @return A proxy
324 */
325 public static HibernateProxy getAssociationProxy(Object obj, String associationName) {
326 try {
327 Object proxy = PropertyUtils.getProperty(obj, associationName);
328 if(proxy instanceof HibernateProxy) return (HibernateProxy) proxy;
329 else return null;
330 }
331 catch (IllegalAccessException e) {
332 return null;
333 }
334 catch (InvocationTargetException e) {
335 return null;
336 }
337 catch (NoSuchMethodException e) {
338 return null;
339 }
340 }
341
342 /**
343 * Checks whether an associated property is initialized and returns true if it is
344 *
345 * @param obj The name of the object
346 * @param associationName The name of the association
347 * @return True if is initialized
348 */
349 public static boolean isInitialized(Object obj, String associationName) {
350 try {
351 Object proxy = PropertyUtils.getProperty(obj, associationName);
352 return Hibernate.isInitialized(proxy);
353 }
354 catch (IllegalAccessException e) {
355 return false;
356 }
357 catch (InvocationTargetException e) {
358 return false;
359 }
360 catch (NoSuchMethodException e) {
361 return false;
362 }
363 }
364
365 public static boolean isCacheQueriesByDefault() {
366 Object o = ConfigurationHolder.getFlatConfig().get(CONFIG_PROPERTY_CACHE_QUERIES);
367 return (o != null && o instanceof Boolean)?((Boolean)o).booleanValue():false;
368 }
369
370 public static GroovyAwareJavassistProxyFactory buildProxyFactory(PersistentClass persistentClass) {
371 GroovyAwareJavassistProxyFactory proxyFactory = new GroovyAwareJavassistProxyFactory();
372
373
374 Set<Class> proxyInterfaces = new HashSet<Class>() {{
375 add(HibernateProxy.class);
376 }
377 };
378
379
380 final Class javaClass = persistentClass.getMappedClass();
381 final Property identifierProperty = persistentClass.getIdentifierProperty();
382 final Getter idGetter = identifierProperty!=null? identifierProperty.getGetter(javaClass) : null;
383 final Setter idSetter =identifierProperty!=null? identifierProperty.getSetter(javaClass) : null;
384
385 if(idGetter == null || idSetter==null) return null;
386 try {
387 proxyFactory.postInstantiate(persistentClass.getEntityName(),
388 javaClass,
389 proxyInterfaces,
390 idGetter.getMethod(),
391 idSetter.getMethod(),
392 persistentClass.hasEmbeddedIdentifier() ?
393 (AbstractComponentType) persistentClass.getIdentifier().getType() :
394 null
395 );
396 }
397 catch (HibernateException e) {
398
399 LOG.warn("Cannot instantiate proxy factory: " + e.getMessage());
400 return null;
401 }
402
403 return proxyFactory;
404 }
405 }