001    /*
002      GRANITE DATA SERVICES
003      Copyright (C) 2011 GRANITE DATA SERVICES S.A.S.
004    
005      This file is part of Granite Data Services.
006    
007      Granite Data Services is free software; you can redistribute it and/or modify
008      it under the terms of the GNU Library General Public License as published by
009      the Free Software Foundation; either version 2 of the License, or (at your
010      option) any later version.
011    
012      Granite Data Services is distributed in the hope that it will be useful, but
013      WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014      FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015      for more details.
016    
017      You should have received a copy of the GNU Library General Public License
018      along with this library; if not, see <http://www.gnu.org/licenses/>.
019    */
020    
021    package org.granite.messaging.amf.io.util.externalizer;
022    
023    import java.beans.Introspector;
024    import java.beans.PropertyDescriptor;
025    import java.io.IOException;
026    import java.io.ObjectInput;
027    import java.io.ObjectOutput;
028    import java.lang.reflect.Constructor;
029    import java.lang.reflect.Field;
030    import java.lang.reflect.InvocationTargetException;
031    import java.lang.reflect.Method;
032    import java.lang.reflect.Modifier;
033    import java.util.ArrayList;
034    import java.util.Collections;
035    import java.util.Comparator;
036    import java.util.HashSet;
037    import java.util.List;
038    import java.util.Map;
039    import java.util.Set;
040    import java.util.concurrent.ConcurrentHashMap;
041    import java.util.concurrent.locks.ReentrantLock;
042    
043    import org.granite.collections.BasicMap;
044    import org.granite.context.GraniteContext;
045    import org.granite.logging.Logger;
046    import org.granite.messaging.amf.io.convert.Converters;
047    import org.granite.messaging.amf.io.util.FieldProperty;
048    import org.granite.messaging.amf.io.util.MethodProperty;
049    import org.granite.messaging.amf.io.util.Property;
050    import org.granite.messaging.amf.io.util.externalizer.annotation.ExternalizedBean;
051    import org.granite.messaging.amf.io.util.externalizer.annotation.ExternalizedProperty;
052    import org.granite.messaging.amf.io.util.externalizer.annotation.IgnoredProperty;
053    import org.granite.messaging.amf.io.util.instantiator.AbstractInstantiator;
054    import org.granite.util.ClassUtil;
055    import org.granite.util.XMap;
056    
057    /**
058     * @author Franck WOLFF
059     */
060    public class DefaultExternalizer implements Externalizer {
061    
062            private static final Logger log = Logger.getLogger(DefaultExternalizer.class);
063            protected static final byte[] BYTES_0 = new byte[0];
064            
065        private final ReentrantLock lock = new ReentrantLock();
066        protected final ConcurrentHashMap<Class<?>, List<Property>> orderedFields =
067            new ConcurrentHashMap<Class<?>, List<Property>>();
068        protected final ConcurrentHashMap<Class<?>, List<Property>> orderedSetterFields =
069            new ConcurrentHashMap<Class<?>, List<Property>>();
070        protected final ConcurrentHashMap<String, Constructor<?>> constructors =
071            new ConcurrentHashMap<String, Constructor<?>>();
072        
073        protected boolean dynamicClass = false;
074        
075    
076        public void configure(XMap properties) {
077            if (properties != null) {
078                    String dynamicclass = properties.get("dynamic-class");
079                    if (Boolean.TRUE.toString().equalsIgnoreCase(dynamicclass))
080                            dynamicClass = true;
081            }
082        }
083        
084        public Object newInstance(final String type, ObjectInput in)
085            throws IOException, ClassNotFoundException, InstantiationException,
086                   InvocationTargetException, IllegalAccessException {
087    
088            Constructor<?> constructor = !dynamicClass ? constructors.get(type) : null;
089    
090            if (constructor == null) {
091                Class<?> clazz = ClassUtil.forName(type);
092                constructor = findDefaultConstructor(clazz);
093                if (!dynamicClass) {
094                        Constructor<?> previousConstructor = constructors.putIfAbsent(type, constructor);
095                        if (previousConstructor != null)
096                            constructor = previousConstructor; // Should be the same instance, anyway...
097                }
098            }
099    
100            return constructor.newInstance();
101        }
102    
103        public void readExternal(Object o, ObjectInput in)
104            throws IOException, ClassNotFoundException, IllegalAccessException {
105            
106            if (o instanceof AbstractInstantiator<?>) {
107                AbstractInstantiator<?> instantiator = (AbstractInstantiator<?>)o;
108                List<String> fields = instantiator.getOrderedFieldNames();
109                log.debug("Reading bean with instantiator %s with fields %s", instantiator.getClass().getName(), fields);
110                for (String fieldName : fields)
111                    instantiator.put(fieldName, in.readObject());
112            }
113            else {
114                List<Property> fields = findOrderedFields(o.getClass());
115                log.debug("Reading bean %s with fields %s", o.getClass().getName(), fields);
116                for (Property field : fields) {
117                    Object value = in.readObject();
118                    if (!(field instanceof MethodProperty && field.isAnnotationPresent(ExternalizedProperty.class)))
119                            field.setProperty(o, value);
120                }
121            }
122        }
123    
124        public void writeExternal(Object o, ObjectOutput out)
125            throws IOException, IllegalAccessException {
126    
127            GraniteContext context = GraniteContext.getCurrentInstance();
128            String instantiatorType = context.getGraniteConfig().getInstantiator(o.getClass().getName());
129            if (instantiatorType != null) {
130                try {
131                    AbstractInstantiator<?> instantiator =
132                        (AbstractInstantiator<?>)ClassUtil.newInstance(instantiatorType);
133                    List<String> fields = instantiator.getOrderedFieldNames();
134                    log.debug("Writing bean with instantiator %s with fields %s", instantiator.getClass().getName(), fields);
135                    for (String fieldName : fields) {
136                        Field field = o.getClass().getDeclaredField(fieldName);
137                        field.setAccessible(true);
138                        out.writeObject(field.get(o));
139                    }
140                } catch (Exception e) {
141                    throw new RuntimeException("Error with instantiatorType: " + instantiatorType, e);
142                }
143            }
144            else {
145                List<Property> fields = findOrderedFields(o.getClass());
146                log.debug("Writing bean %s with fields %s", o.getClass().getName(), fields);
147                for (Property field : fields) {
148                    Object value = field.getProperty(o);
149                    if (value instanceof Map<?, ?>)
150                        value = BasicMap.newInstance((Map<?, ?>)value);
151                    if (isValueIgnored(value))
152                            out.writeObject(null);
153                    else
154                            out.writeObject(value);
155                }
156            }
157        }
158        
159        protected boolean isValueIgnored(Object value) {
160            return false;
161        }
162    
163        public List<Property> findOrderedFields(final Class<?> clazz) {
164            return findOrderedFields(clazz, false);
165        }
166        
167        public List<Property> findOrderedFields(final Class<?> clazz, boolean returnSettersWhenAvailable) {
168            List<Property> fields = !dynamicClass ? (returnSettersWhenAvailable ? orderedSetterFields.get(clazz) : orderedFields.get(clazz)) : null;
169    
170            if (fields == null) {
171                    if (dynamicClass)
172                            Introspector.flushFromCaches(clazz);
173                PropertyDescriptor[] propertyDescriptors = ClassUtil.getProperties(clazz);
174                Converters converters = GraniteContext.getCurrentInstance().getGraniteConfig().getConverters();
175    
176                fields = new ArrayList<Property>();
177    
178                Set<String> allFieldNames = new HashSet<String>();
179                for (Class<?> c = clazz; c != null; c = c.getSuperclass()) {
180    
181                    List<Property> newFields = new ArrayList<Property>();
182    
183                    // Standard declared fields.
184                    for (Field field : c.getDeclaredFields()) {
185                        if (!allFieldNames.contains(field.getName()) &&
186                            !Modifier.isTransient(field.getModifiers()) &&
187                            !Modifier.isStatic(field.getModifiers()) &&
188                            !isPropertyIgnored(field) &&
189                            !field.isAnnotationPresent(IgnoredProperty.class)) {
190    
191                            boolean found = false;
192                            if (returnSettersWhenAvailable && propertyDescriptors != null) {
193                                    for (PropertyDescriptor pd : propertyDescriptors) {
194                                            if (pd.getName().equals(field.getName()) && pd.getWriteMethod() != null) {
195                                                    newFields.add(new MethodProperty(converters, field.getName(), pd.getWriteMethod(), pd.getReadMethod()));
196                                                    found = true;
197                                                    break;
198                                            }
199                                    }
200                            }
201                                    if (!found)
202                                    newFields.add(new FieldProperty(converters, field));
203                        }
204                        allFieldNames.add(field.getName());
205                    }
206    
207                    // Getter annotated  by @ExternalizedProperty.
208                    if (propertyDescriptors != null) {
209                        for (PropertyDescriptor property : propertyDescriptors) {
210                            Method getter = property.getReadMethod();
211                            if (getter != null &&
212                                getter.isAnnotationPresent(ExternalizedProperty.class) &&
213                                getter.getDeclaringClass().equals(c) &&
214                                !allFieldNames.contains(property.getName())) {
215    
216                                newFields.add(new MethodProperty(
217                                    converters,
218                                    property.getName(),
219                                    null,
220                                    getter
221                                ));
222                                allFieldNames.add(property.getName());
223                            }
224                        }
225                    }
226    
227                    Collections.sort(newFields, new Comparator<Property>() {
228                        public int compare(Property o1, Property o2) {
229                            return o1.getName().compareTo(o2.getName());
230                        }
231                    });
232    
233                    fields.addAll(0, newFields);
234                }
235    
236                if (!dynamicClass) {
237                        List<Property> previousFields = (returnSettersWhenAvailable ? orderedSetterFields : orderedFields).putIfAbsent(clazz, fields);
238                        if (previousFields != null)
239                            fields = previousFields;
240                }
241            }
242    
243            return fields;
244        }
245        
246        protected boolean isPropertyIgnored(Field field) {
247            return false;
248        }
249    
250        protected <T> Constructor<T> findDefaultConstructor(Class<T> clazz) {
251            Constructor<T> constructor = null;
252    
253            GraniteContext context = GraniteContext.getCurrentInstance();
254            String instantiator = context.getGraniteConfig().getInstantiator(clazz.getName());
255            if (instantiator != null) {
256                try {
257                    Class<T> instantiatorClass = ClassUtil.forName(instantiator, clazz);
258                    constructor = instantiatorClass.getConstructor();
259                } catch (ClassNotFoundException e) {
260                    throw new RuntimeException(
261                        "Could not load instantiator class: " + instantiator + " for: " + clazz.getName(), e
262                    );
263                } catch (NoSuchMethodException e) {
264                    throw new RuntimeException(
265                        "Could not find default constructor in instantiator class: " + instantiator, e
266                    );
267                }
268            }
269            else {
270                try {
271                    constructor = clazz.getConstructor();
272                } catch (NoSuchMethodException e) {
273                    // fall down...
274                }
275    
276                if (constructor == null) {
277                    String key = DefaultConstructorFactory.class.getName();
278                    DefaultConstructorFactory factory = getDefaultConstructorFactory(context, key);
279                    constructor = factory.findDefaultConstructor(clazz);
280                }
281            }
282    
283            return constructor;
284        }
285    
286        private DefaultConstructorFactory getDefaultConstructorFactory(
287            GraniteContext context,
288            String key) {
289    
290            lock.lock();
291            try {
292                DefaultConstructorFactory factory =
293                    (DefaultConstructorFactory)context.getApplicationMap().get(key);
294                if (factory == null) {
295                    try {
296                        factory = new SunDefaultConstructorFactory();
297                    } catch (Exception e) {
298                        // fall down...
299                    }
300                    if (factory == null)
301                        factory = new NoDefaultConstructorFactory();
302                    context.getApplicationMap().put(key, factory);
303                }
304                return factory;
305            } finally {
306                lock.unlock();
307            }
308        }
309    
310        public int accept(Class<?> clazz) {
311            return clazz.isAnnotationPresent(ExternalizedBean.class) ? 0 : -1;
312        }
313    }
314    
315    interface DefaultConstructorFactory {
316        public <T> Constructor<T> findDefaultConstructor(Class<T> clazz);
317    }
318    
319    class NoDefaultConstructorFactory implements DefaultConstructorFactory {
320    
321        public <T> Constructor<T> findDefaultConstructor(Class<T> clazz) {
322            throw new RuntimeException("Could not find default constructor in class: " + clazz);
323        }
324    }
325    
326    class SunDefaultConstructorFactory implements DefaultConstructorFactory {
327    
328        private final Object reflectionFactory;
329        private final Method newConstructorForSerialization;
330    
331        public SunDefaultConstructorFactory() {
332            try {
333                Class<?> factoryClass = ClassUtil.forName("sun.reflect.ReflectionFactory");
334                Method getReflectionFactory = factoryClass.getDeclaredMethod("getReflectionFactory");
335                reflectionFactory = getReflectionFactory.invoke(null);
336                newConstructorForSerialization = factoryClass.getDeclaredMethod(
337                    "newConstructorForSerialization",
338                    new Class[]{Class.class, Constructor.class}
339                );
340            } catch (Exception e) {
341                throw new RuntimeException("Could not create Sun Factory", e);
342            }
343        }
344    
345        @SuppressWarnings("unchecked")
346        public <T> Constructor<T> findDefaultConstructor(Class<T> clazz) {
347            try {
348                Constructor<?> constructor = Object.class.getDeclaredConstructor();
349                constructor = (Constructor<?>)newConstructorForSerialization.invoke(
350                    reflectionFactory,
351                    new Object[]{clazz, constructor}
352                );
353                constructor.setAccessible(true);
354                return (Constructor<T>)constructor;
355            } catch (Exception e) {
356                throw new RuntimeException(e);
357            }
358        }
359    }