001/** 002 * GRANITE DATA SERVICES 003 * Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S. 004 * 005 * This file is part of the Granite Data Services Platform. 006 * 007 * Granite Data Services is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * Granite Data Services is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 015 * General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 020 * USA, or see <http://www.gnu.org/licenses/>. 021 */ 022package org.granite.hibernate4; 023 024import java.io.Serializable; 025import java.lang.reflect.Field; 026import java.lang.reflect.Method; 027import java.lang.reflect.Type; 028import java.util.concurrent.ConcurrentHashMap; 029 030import javax.persistence.EmbeddedId; 031import javax.persistence.Id; 032 033import org.granite.config.GraniteConfig; 034import org.granite.context.GraniteContext; 035import org.granite.messaging.service.ServiceException; 036import org.granite.util.TypeUtil; 037import org.granite.util.Introspector; 038import org.granite.util.PropertyDescriptor; 039import org.hibernate.engine.spi.SessionImplementor; 040import org.hibernate.proxy.HibernateProxy; 041import org.hibernate.type.CompositeType; 042 043/** 044 * @author Franck WOLFF 045 */ 046public class ProxyFactory { 047 048 private static final Class<?>[] INTERFACES = new Class<?>[]{HibernateProxy.class}; 049 private static final Class<?>[] SINGLE_OBJECT_PARAMS = new Class<?>[]{Object.class}; 050 private static final Method OBJECT_EQUALS; 051 static { 052 try { 053 OBJECT_EQUALS = Object.class.getMethod("equals", SINGLE_OBJECT_PARAMS); 054 } 055 catch (Exception e) { 056 throw new ExceptionInInitializerError(e); 057 } 058 } 059 060 protected final ConcurrentHashMap<Class<?>, Object[]> identifierInfos = new ConcurrentHashMap<Class<?>, Object[]>(); 061 062 private final Method getProxyFactory; 063 private final Method getProxy; 064 private final boolean classOverridesEqualsParameter; 065 066 public ProxyFactory(String initializerClassName) { 067 try { 068 // Get proxy methods: even if CGLIB/Javassist LazyInitializer implementations share a common 069 // superclass, getProxyFactory/getProxy methods are declared as static in each inherited 070 // class with the same signature. 071 Class<?> initializerClass = TypeUtil.forName(initializerClassName); 072 getProxyFactory = initializerClass.getMethod("getProxyFactory", new Class[] { Class.class, Class[].class }); 073 074 // Hibernate 4.0.1 has an extra boolean parameter in last position: classOverridesEquals. 075 Method getProxy = null; 076 boolean classOverridesEqualsParameter = false; 077 try { 078 getProxy = initializerClass.getMethod("getProxy", new Class[]{ 079 Class.class, String.class, Class.class, Class[].class, Method.class, Method.class, 080 CompositeType.class, Serializable.class, SessionImplementor.class 081 }); 082 } 083 catch (NoSuchMethodException e) { 084 getProxy = initializerClass.getMethod("getProxy", new Class[]{ 085 Class.class, String.class, Class.class, Class[].class, Method.class, Method.class, 086 CompositeType.class, Serializable.class, SessionImplementor.class, Boolean.TYPE 087 }); 088 classOverridesEqualsParameter = true; 089 } 090 this.getProxy = getProxy; 091 this.classOverridesEqualsParameter = classOverridesEqualsParameter; 092 } 093 catch (Exception e) { 094 throw new ServiceException("Could not introspect initializer class: " + initializerClassName, e); 095 } 096 } 097 098 public HibernateProxy getProxyInstance(String persistentClassName, String entityName, Serializable id) { 099 try { 100 // Get ProxyFactory. 101 Class<?> persistentClass = TypeUtil.forName(persistentClassName); 102 Class<?> factory = (Class<?>)getProxyFactory.invoke(null, new Object[] { persistentClass, INTERFACES }); 103 104 // Convert id (if necessary). 105 Object[] identifierInfo = getIdentifierInfo(persistentClass); 106 Type identifierType = (Type)identifierInfo[0]; 107 Method identifierGetter = (Method)identifierInfo[1]; 108 if (id == null || !identifierType.equals(id.getClass())) { 109 GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig(); 110 id = (Serializable)config.getConverters().convert(id, identifierType); 111 } 112 113 // Get Proxy (with or without the extra parameter classOverridesEquals) 114 if (classOverridesEqualsParameter) { 115 return (HibernateProxy)getProxy.invoke(null, new Object[]{ 116 factory, entityName, persistentClass, INTERFACES, identifierGetter, null, null, id, null, overridesEquals(persistentClass)}); 117 } 118 return (HibernateProxy)getProxy.invoke(null, new Object[] { factory, entityName, persistentClass, INTERFACES, identifierGetter, null, null, id, null }); 119 } 120 catch (Exception e) { 121 throw new ServiceException("Error with proxy description: " + persistentClassName + '/' + entityName + " and id: " + id, e); 122 } 123 } 124 125 protected boolean overridesEquals(Class<?> persistentClass) { 126 try { 127 return !OBJECT_EQUALS.equals(persistentClass.getMethod("equals", SINGLE_OBJECT_PARAMS)); 128 } 129 catch (Exception e) { 130 return false; // should never happen unless persistentClass is an interface... 131 } 132 } 133 134 protected Object[] getIdentifierInfo(Class<?> persistentClass) { 135 136 Object[] info = identifierInfos.get(persistentClass); 137 if (info != null) 138 return info; 139 140 Type type = null; 141 Method getter = null; 142 for (Class<?> clazz = persistentClass; clazz != Object.class && clazz != null; clazz = clazz.getSuperclass()) { 143 for (Field field : clazz.getDeclaredFields()) { 144 if (field.isAnnotationPresent(Id.class) || field.isAnnotationPresent(EmbeddedId.class)) { 145 type = field.getGenericType(); 146 break; 147 } 148 } 149 } 150 151 if (type == null) { 152 PropertyDescriptor[] propertyDescriptors = Introspector.getPropertyDescriptors(persistentClass); 153 154 for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { 155 Method method = propertyDescriptor.getReadMethod(); 156 if (method != null && ( 157 method.isAnnotationPresent(Id.class) || 158 method.isAnnotationPresent(EmbeddedId.class))) { 159 type = method.getGenericReturnType(); 160 getter = method; 161 break; 162 } 163 method = propertyDescriptor.getWriteMethod(); 164 if (method != null && ( 165 method.isAnnotationPresent(Id.class) || 166 method.isAnnotationPresent(EmbeddedId.class))) { 167 type = method.getGenericParameterTypes()[0]; 168 break; 169 } 170 } 171 } 172 173 if (type != null) { 174 info = new Object[] { type, getter }; 175 Object[] previousInfo = identifierInfos.putIfAbsent(persistentClass, info); 176 if (previousInfo != null) 177 info = previousInfo; // should be the same... 178 return info; 179 } 180 181 throw new IllegalArgumentException("Could not find id in: " + persistentClass); 182 } 183}