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     */
022    package org.granite.hibernate;
023    
024    import java.io.Serializable;
025    import java.lang.reflect.Field;
026    import java.lang.reflect.Method;
027    import java.lang.reflect.Type;
028    import java.util.concurrent.ConcurrentHashMap;
029    
030    import javax.persistence.EmbeddedId;
031    import javax.persistence.Id;
032    
033    import org.granite.config.GraniteConfig;
034    import org.granite.context.GraniteContext;
035    import org.granite.messaging.service.ServiceException;
036    import org.granite.util.TypeUtil;
037    import org.granite.util.Introspector;
038    import org.granite.util.PropertyDescriptor;
039    import org.hibernate.engine.SessionImplementor;
040    import org.hibernate.proxy.HibernateProxy;
041    import org.hibernate.type.AbstractComponentType;
042    
043    /**
044     * @author Franck WOLFF
045     */
046    @SuppressWarnings("deprecation")
047    public class ProxyFactory {
048    
049        private static final Class<?>[] INTERFACES = new Class[]{HibernateProxy.class};
050    
051        protected final ConcurrentHashMap<Class<?>, Object[]> identifierInfos = new ConcurrentHashMap<Class<?>, Object[]>();
052    
053        private final Method getProxyFactory;
054        private final Method getProxy;
055    
056        public ProxyFactory(String initializerClassName) {
057            try {
058                // Get proxy methods: even if CGLIB/Javassist LazyInitializer implementations share a common
059                    // superclass, getProxyFactory/getProxy methods are declared as static in each inherited
060                    // class with the same signature.
061                Class<?> initializerClass = TypeUtil.forName(initializerClassName);
062                getProxyFactory = initializerClass.getMethod("getProxyFactory", new Class[]{Class.class, Class[].class});
063                            Class<?> componentTypeClass = AbstractComponentType.class;
064                try {
065                    // Hibernate 3.6
066                    componentTypeClass = TypeUtil.forName("org.hibernate.type.CompositeType");
067                }
068                catch (ClassNotFoundException e) {
069                    // Hibernate until 3.5 
070                }
071                getProxy = initializerClass.getMethod("getProxy", new Class[]{
072                    Class.class, String.class, Class.class, Class[].class, Method.class, Method.class,
073                    componentTypeClass, Serializable.class, SessionImplementor.class
074                });
075            } 
076            catch (Exception e) {
077                throw new ServiceException("Could not introspect initializer class: " + initializerClassName, e);
078            }
079        }
080    
081        public HibernateProxy getProxyInstance(String persistentClassName, String entityName, Serializable id) {
082            try {
083                // Get ProxyFactory.
084                Class<?> persistentClass = TypeUtil.forName(persistentClassName);
085                Class<?> factory = (Class<?>)getProxyFactory.invoke(null, new Object[]{persistentClass, INTERFACES});
086    
087                // Convert id (if necessary).
088                Object[] identifierInfo = getIdentifierInfo(persistentClass);
089                Type identifierType = (Type)identifierInfo[0];
090                Method identifierGetter = (Method)identifierInfo[1];
091                if (id == null || !identifierType.equals(id.getClass())) {
092                    GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
093                    id = (Serializable)config.getConverters().convert(id, identifierType);
094                }
095    
096                // Get Proxy
097                return (HibernateProxy)getProxy.invoke(null, new Object[]{factory, entityName, persistentClass, INTERFACES, identifierGetter, null, null, id, null});
098            } 
099            catch (Exception e) {
100                throw new ServiceException("Error with proxy description: " + persistentClassName + '/' + entityName + " and id: " + id, e);
101            }
102        }
103    
104        protected Object[] getIdentifierInfo(Class<?> persistentClass) {
105    
106            Object[] info = identifierInfos.get(persistentClass);
107            if (info != null)
108                return info;
109    
110            Type type = null;
111            Method getter = null;
112            for (Class<?> clazz = persistentClass; clazz != Object.class && clazz != null; clazz = clazz.getSuperclass()) {
113                for (Field field : clazz.getDeclaredFields()) {
114                    if (field.isAnnotationPresent(Id.class) || field.isAnnotationPresent(EmbeddedId.class)) {
115                        type = field.getGenericType();
116                        break;
117                    }
118                }
119            }
120    
121            if (type == null) {
122                PropertyDescriptor[] propertyDescriptors = Introspector.getPropertyDescriptors(persistentClass);
123    
124                for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
125                    Method method = propertyDescriptor.getReadMethod();
126                    if (method != null && (
127                            method.isAnnotationPresent(Id.class) ||
128                            method.isAnnotationPresent(EmbeddedId.class))) {
129                        type = method.getGenericReturnType();
130                        getter = method;
131                        break;
132                    }
133                    method = propertyDescriptor.getWriteMethod();
134                    if (method != null && (
135                            method.isAnnotationPresent(Id.class) ||
136                            method.isAnnotationPresent(EmbeddedId.class))) {
137                        type = method.getGenericParameterTypes()[0];
138                        break;
139                    }
140                }
141            }
142    
143            if (type != null) {
144                    info = new Object[] { type, getter };
145                Object[] previousInfo = identifierInfos.putIfAbsent(persistentClass, info);
146                if (previousInfo != null)
147                    info = previousInfo; // should be the same...
148                return info;
149            }
150    
151            throw new IllegalArgumentException("Could not find id in: " + persistentClass);
152        }
153    }