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.ByteArrayInputStream;
025    import java.io.ByteArrayOutputStream;
026    import java.io.IOException;
027    import java.io.ObjectInput;
028    import java.io.ObjectInputStream;
029    import java.io.ObjectOutput;
030    import java.io.ObjectOutputStream;
031    import java.io.Serializable;
032    import java.lang.reflect.InvocationTargetException;
033    import java.lang.reflect.ParameterizedType;
034    import java.lang.reflect.Type;
035    import java.util.Comparator;
036    import java.util.List;
037    import java.util.Map;
038    import java.util.Set;
039    import java.util.SortedMap;
040    import java.util.SortedSet;
041    import java.util.concurrent.ConcurrentHashMap;
042    
043    import javax.persistence.Embeddable;
044    import javax.persistence.Entity;
045    import javax.persistence.MappedSuperclass;
046    
047    import org.granite.collections.BasicMap;
048    import org.granite.config.GraniteConfig;
049    import org.granite.context.GraniteContext;
050    import org.granite.logging.Logger;
051    import org.granite.messaging.amf.io.convert.Converters;
052    import org.granite.messaging.amf.io.util.ClassGetter;
053    import org.granite.messaging.amf.io.util.MethodProperty;
054    import org.granite.messaging.amf.io.util.Property;
055    import org.granite.messaging.amf.io.util.externalizer.DefaultExternalizer;
056    import org.granite.messaging.annotations.Include;
057    import org.granite.messaging.persistence.AbstractExternalizablePersistentCollection;
058    import org.granite.messaging.persistence.ExternalizablePersistentBag;
059    import org.granite.messaging.persistence.ExternalizablePersistentList;
060    import org.granite.messaging.persistence.ExternalizablePersistentMap;
061    import org.granite.messaging.persistence.ExternalizablePersistentSet;
062    import org.granite.util.StringUtil;
063    import org.granite.util.TypeUtil;
064    import org.granite.util.XMap;
065    import org.hibernate.Hibernate;
066    import org.hibernate.annotations.Sort;
067    import org.hibernate.annotations.SortType;
068    import org.hibernate.collection.PersistentBag;
069    import org.hibernate.collection.PersistentCollection;
070    import org.hibernate.collection.PersistentList;
071    import org.hibernate.collection.PersistentMap;
072    import org.hibernate.collection.PersistentSet;
073    import org.hibernate.collection.PersistentSortedMap;
074    import org.hibernate.collection.PersistentSortedSet;
075    import org.hibernate.proxy.HibernateProxy;
076    import org.hibernate.proxy.LazyInitializer;
077    
078    /**
079     * @author Franck WOLFF
080     */
081    public class HibernateExternalizer extends DefaultExternalizer {
082    
083            private static final Logger log = Logger.getLogger(HibernateExternalizer.class);
084            
085        private final ConcurrentHashMap<String, ProxyFactory> proxyFactories = new ConcurrentHashMap<String, ProxyFactory>();
086        
087        static enum SerializeMetadata {
088            YES,
089            NO,
090            LAZY
091        }
092        
093        private SerializeMetadata serializeMetadata = SerializeMetadata.NO;
094        
095    
096        /**
097         * Configure this externalizer with the values supplied in granite-config.xml.
098         * 
099         * <p>The only supported configuration option is 'hibernate-collection-metadata' with
100         * values in ['no' (default), 'yes' and 'lazy']. By default, collection metadata (key,
101         * role and snapshot) aren't serialized. If the value of the 'hibernate-collection-metadata'
102         * node is 'yes', metadata will be always serialized, while the 'lazy' value tells the
103         * externalizer to serialiaze metadata for uninitialized collections only.
104         * 
105         * <p>Configuration example (granite-config.xml):
106         * <pre>
107         * &lt;granite-config scan="true"&gt;
108         *   &lt;externalizers&gt;
109         *     &lt;configuration&gt;
110         *       &lt;hibernate-collection-metadata&gt;lazy&lt;/hibernate-collection-metadata&gt;
111         *     &lt;/configuration&gt;
112         *   &lt;/externalizers&gt;
113         * &lt;/granite-config&gt;
114         * </pre>
115         * 
116         * @param properties an XMap instance that contains the configuration node.
117         */
118        @Override
119            public void configure(XMap properties) {
120            super.configure(properties);
121            
122            if (properties != null) {
123                    String collectionmetadata = properties.get("hibernate-collection-metadata");
124                    if (collectionmetadata != null) {
125                            if ("no".equalsIgnoreCase(collectionmetadata))
126                                    serializeMetadata = SerializeMetadata.NO;
127                            else if ("yes".equalsIgnoreCase(collectionmetadata))
128                                    serializeMetadata = SerializeMetadata.YES;
129                            else if ("lazy".equalsIgnoreCase(collectionmetadata))
130                                    serializeMetadata = SerializeMetadata.LAZY;
131                            else
132                                    throw new RuntimeException("Illegal value for the 'hibernate-collection-metadata' option: " + collectionmetadata);
133                    }
134            }
135            }
136    
137            @Override
138        public Object newInstance(String type, ObjectInput in)
139            throws IOException, ClassNotFoundException, InstantiationException, InvocationTargetException, IllegalAccessException {
140    
141            // If type is not an entity (@Embeddable for example), we don't read initialized/detachedState
142            // and we fall back to DefaultExternalizer behavior.
143            Class<?> clazz = TypeUtil.forName(type);
144            if (!isRegularEntity(clazz))
145                return super.newInstance(type, in);
146            
147            // Read initialized flag.
148            boolean initialized = ((Boolean)in.readObject()).booleanValue();
149    
150            // Read detachedState.
151            String detachedState = (String)in.readObject();
152            
153            // New or initialized entity.
154            if (initialized)
155                return super.newInstance(type, in);
156    
157            // Actual proxy instantiation is deferred in order to keep consistent order in
158            // stored objects list (see AMF3Deserializer).
159            return newProxyInstantiator(proxyFactories, detachedState);
160        }
161            
162            protected Object newProxyInstantiator(ConcurrentHashMap<String, ProxyFactory> proxyFactories, String detachedState) {
163            return new HibernateProxyInstantiator(proxyFactories, detachedState);
164            }
165    
166        @Override
167        public void readExternal(Object o, ObjectInput in) throws IOException, ClassNotFoundException, IllegalAccessException {
168    
169            // Skip unserialized fields for proxies (only read id).
170            if (o instanceof HibernateProxyInstantiator) {
171                    log.debug("Reading Hibernate Proxy...");
172                ((HibernateProxyInstantiator)o).readId(in);
173            }
174            // @Embeddable or others...
175            else if (!isRegularEntity(o.getClass()) && !isEmbeddable(o.getClass())) {
176                    log.debug("Delegating non regular entity reading to DefaultExternalizer...");
177                super.readExternal(o, in);
178            }
179            // Regular @Entity or @MappedSuperclass
180            else {
181                GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
182    
183                Converters converters = config.getConverters();
184                ClassGetter classGetter = config.getClassGetter();
185                Class<?> oClass = classGetter.getClass(o);
186                ParameterizedType[] declaringTypes = TypeUtil.getDeclaringTypes(oClass);
187    
188                List<Property> fields = findOrderedFields(oClass, false);
189                log.debug("Reading entity %s with fields %s", oClass.getName(), fields);
190                for (Property field : fields) {
191                    Object value = in.readObject();
192                    if (!(field instanceof MethodProperty && field.isAnnotationPresent(Include.class, true))) {
193                        
194                            if (value instanceof AbstractExternalizablePersistentCollection)
195                                    value = newHibernateCollection((AbstractExternalizablePersistentCollection)value, field);
196                        else if (!(value instanceof HibernateProxy)) {
197                            Type targetType = TypeUtil.resolveTypeVariable(field.getType(), field.getDeclaringClass(), declaringTypes);
198                            value = converters.convert(value, targetType);
199                        }
200    
201                            field.setProperty(o, value, false);
202                    }
203                }
204            }
205        }
206        
207            protected PersistentCollection newHibernateCollection(AbstractExternalizablePersistentCollection value, Property field) {
208            final Type target = field.getType();
209            final boolean initialized = value.isInitialized();
210            final String metadata = value.getMetadata();
211                    final boolean dirty = value.isDirty();
212            final boolean sorted = (
213                    SortedSet.class.isAssignableFrom(TypeUtil.classOfType(target)) ||
214                    SortedMap.class.isAssignableFrom(TypeUtil.classOfType(target))
215            );
216            
217            Comparator<?> comparator = null;
218            if (sorted && field.isAnnotationPresent(Sort.class)) {
219                    Sort sort = field.getAnnotation(Sort.class);
220                    if (sort.type() == SortType.COMPARATOR) {
221                            try {
222                                    comparator = TypeUtil.newInstance(sort.comparator(), Comparator.class);
223                            } catch (Exception e) {
224                                    throw new RuntimeException("Could not create instance of Comparator: " + sort.comparator());
225                            }
226                    }
227            }
228            
229            PersistentCollection coll = null;
230                    if (value instanceof ExternalizablePersistentSet) {
231                    if (initialized) {
232                            Set<?> set = ((ExternalizablePersistentSet)value).getContentAsSet(target, comparator);
233                            coll = (sorted ? new PersistentSortedSet(null, (SortedSet<?>)set) : new PersistentSet(null, set));
234                }
235                    else
236                    coll = (sorted ? new PersistentSortedSet() : new PersistentSet());
237            }
238                    else if (value instanceof ExternalizablePersistentBag) {
239                    if (initialized) {
240                        List<?> bag = ((ExternalizablePersistentBag)value).getContentAsList(target);
241                    coll = new PersistentBag(null, bag);
242                    }
243                    else
244                        coll = new PersistentBag();
245                    }
246                    else if (value instanceof ExternalizablePersistentList) {
247                    if (initialized) {
248                        List<?> list = ((ExternalizablePersistentList)value).getContentAsList(target);
249                    coll = new PersistentList(null, list);
250                    }
251                    else
252                        coll = new PersistentList();
253                    }
254                    else if (value instanceof ExternalizablePersistentMap) {
255                    if (initialized) {
256                        Map<?, ?> map = ((ExternalizablePersistentMap)value).getContentAsMap(target, comparator);
257                        coll = (sorted ? new PersistentSortedMap(null, (SortedMap<?, ?>)map) : new PersistentMap(null, map));
258                    }
259                    else
260                        coll = (sorted ? new PersistentSortedMap() : new PersistentMap());
261                    }
262                    else
263                            throw new RuntimeException("Illegal externalizable persitent class: " + value);
264                    
265                    if (metadata != null && serializeMetadata != SerializeMetadata.NO && (serializeMetadata == SerializeMetadata.YES || !initialized)) {
266                    String[] toks = metadata.split(":", 3);
267                    if (toks.length != 3)
268                            throw new RuntimeException("Invalid collection metadata: " + metadata);
269                    Serializable key = deserializeSerializable(StringUtil.hexStringToBytes(toks[0]));
270                    Serializable snapshot = deserializeSerializable(StringUtil.hexStringToBytes(toks[1]));
271                    String role = toks[2];
272                coll.setSnapshot(key, role, snapshot);
273                    }
274                    
275                    if (initialized && dirty)
276                            coll.dirty();
277            
278            return coll;
279        }
280    
281        @Override
282        public void writeExternal(Object o, ObjectOutput out) throws IOException, IllegalAccessException {
283    
284            ClassGetter classGetter = GraniteContext.getCurrentInstance().getGraniteConfig().getClassGetter();
285            Class<?> oClass = classGetter.getClass(o);
286    
287            String detachedState = null;
288            
289            if (o instanceof HibernateProxy) {              
290                HibernateProxy proxy = (HibernateProxy)o;
291                detachedState = getProxyDetachedState(proxy);
292    
293                // Only write initialized flag & detachedState & entity id if proxy is uninitialized.
294                if (proxy.getHibernateLazyInitializer().isUninitialized()) {
295                    Serializable id = proxy.getHibernateLazyInitializer().getIdentifier();
296                    log.debug("Writing uninitialized HibernateProxy %s with id %s", detachedState, id);
297                    
298                    // Write initialized flag.
299                    out.writeObject(Boolean.FALSE);
300                    // Write detachedState.
301                    out.writeObject(detachedState);
302                    // Write entity id.
303                    out.writeObject(id);
304                    return;
305                }
306    
307                // Proxy is initialized, get the underlying persistent object.
308                    log.debug("Writing initialized HibernateProxy...");
309                o = proxy.getHibernateLazyInitializer().getImplementation();
310            }
311    
312            if (!isRegularEntity(o.getClass()) && !isEmbeddable(o.getClass())) { // @Embeddable or others...
313                    log.debug("Delegating non regular entity writing to DefaultExternalizer...");
314                super.writeExternal(o, out);
315            }
316            else {
317                    if (isRegularEntity(o.getClass())) {
318                        // Write initialized flag.
319                        out.writeObject(Boolean.TRUE);
320                        // Write detachedState.
321                        out.writeObject(detachedState);
322                    }
323                    
324                // Externalize entity fields.
325                List<Property> fields = findOrderedFields(oClass, false);
326                log.debug("Writing entity %s with fields %s", o.getClass().getName(), fields);
327                for (Property field : fields) {
328                    Object value = field.getProperty(o);
329                    
330                    // Persistent collections.
331                    if (value instanceof PersistentCollection)
332                            value = newExternalizableCollection((PersistentCollection)value);
333                    // Transient maps.
334                    else if (value instanceof Map<?, ?>)
335                            value = BasicMap.newInstance((Map<?, ?>)value);
336    
337                    if (isValueIgnored(value))
338                            out.writeObject(null);
339                    else
340                            out.writeObject(value);
341                }
342            }
343        }
344        
345        protected AbstractExternalizablePersistentCollection newExternalizableCollection(PersistentCollection value) {
346            final boolean initialized = Hibernate.isInitialized(value);
347            final boolean dirty = value.isDirty();
348            
349            AbstractExternalizablePersistentCollection coll = null;
350            
351            if (value instanceof PersistentSet)
352                coll = new ExternalizablePersistentSet(initialized ? (Set<?>)value : null, initialized, dirty);
353            else if (value instanceof PersistentList)
354                coll = new ExternalizablePersistentList(initialized ? (List<?>)value : null, initialized, dirty);
355            else if (value instanceof PersistentBag)
356                coll = new ExternalizablePersistentBag(initialized ? (List<?>)value : null, initialized, dirty);
357            else if (value instanceof PersistentMap)
358                coll = new ExternalizablePersistentMap(initialized ? (Map<?, ?>)value : null, initialized, dirty);
359            else
360                throw new UnsupportedOperationException("Unsupported Hibernate collection type: " + value);
361    
362            if (serializeMetadata != SerializeMetadata.NO && (serializeMetadata == SerializeMetadata.YES || !initialized) && value.getRole() != null) {
363                    char[] hexKey = StringUtil.bytesToHexChars(serializeSerializable(value.getKey()));
364                    char[] hexSnapshot = StringUtil.bytesToHexChars(serializeSerializable(value.getStoredSnapshot()));
365                    String metadata = new StringBuilder(hexKey.length + 1 + hexSnapshot.length + 1 + value.getRole().length())
366                                    .append(hexKey).append(':')
367                            .append(hexSnapshot).append(':')
368                            .append(value.getRole())
369                            .toString();
370                    coll.setMetadata(metadata);
371            }
372            
373            return coll;
374        }
375    
376        @Override
377        public int accept(Class<?> clazz) {
378            return (
379                clazz.isAnnotationPresent(Entity.class) ||
380                clazz.isAnnotationPresent(MappedSuperclass.class) ||
381                clazz.isAnnotationPresent(Embeddable.class)
382            ) ? 1 : -1;
383        }
384    
385        protected String getProxyDetachedState(HibernateProxy proxy) {
386            LazyInitializer initializer = proxy.getHibernateLazyInitializer();
387    
388            StringBuilder sb = new StringBuilder();
389    
390            sb.append(initializer.getClass().getName())
391              .append(':');
392            if (initializer.getPersistentClass() != null)
393                sb.append(initializer.getPersistentClass().getName());
394            sb.append(':');
395            if (initializer.getEntityName() != null)
396                sb.append(initializer.getEntityName());
397    
398            return sb.toString();
399        }
400    
401        protected boolean isRegularEntity(Class<?> clazz) {
402            return clazz.isAnnotationPresent(Entity.class) || clazz.isAnnotationPresent(MappedSuperclass.class);
403        }
404        
405        protected boolean isEmbeddable(Class<?> clazz) {
406            return clazz.isAnnotationPresent(Embeddable.class);
407        }
408        
409        protected byte[] serializeSerializable(Serializable o) {
410            if (o == null)
411                    return BYTES_0;
412            try {
413                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
414                    ObjectOutputStream oos = new ObjectOutputStream(baos);
415                    oos.writeObject(o);
416                    return baos.toByteArray();
417            } catch (Exception e) {
418                    throw new RuntimeException("Could not serialize: " + o);
419            }
420        }
421        
422        protected Serializable deserializeSerializable(byte[] data) {
423            if (data.length == 0)
424                    return null;
425            try {
426                    ByteArrayInputStream baos = new ByteArrayInputStream(data);
427                    ObjectInputStream oos = new ObjectInputStream(baos);
428                    return (Serializable)oos.readObject();
429            } catch (Exception e) {
430                    throw new RuntimeException("Could not deserialize: " + data);
431            }
432        }
433    }