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