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
021package org.granite.messaging.amf.io.util.externalizer;
022
023import java.io.IOException;
024import java.io.ObjectInput;
025import java.io.ObjectOutput;
026import java.lang.reflect.Constructor;
027import java.lang.reflect.Field;
028import java.lang.reflect.InvocationTargetException;
029import java.lang.reflect.Method;
030import java.lang.reflect.Modifier;
031import java.util.ArrayList;
032import java.util.Collections;
033import java.util.Comparator;
034import java.util.HashSet;
035import java.util.List;
036import java.util.Map;
037import java.util.Set;
038import java.util.concurrent.ConcurrentHashMap;
039import java.util.concurrent.locks.ReentrantLock;
040
041import org.granite.collections.BasicMap;
042import org.granite.context.GraniteContext;
043import org.granite.logging.Logger;
044import org.granite.messaging.amf.io.convert.Converters;
045import org.granite.messaging.amf.io.util.FieldProperty;
046import org.granite.messaging.amf.io.util.MethodProperty;
047import org.granite.messaging.amf.io.util.Property;
048import org.granite.messaging.amf.io.util.externalizer.annotation.ExternalizedBean;
049import org.granite.messaging.amf.io.util.externalizer.annotation.ExternalizedProperty;
050import org.granite.messaging.amf.io.util.externalizer.annotation.IgnoredProperty;
051import org.granite.messaging.amf.io.util.instantiator.AbstractInstantiator;
052import org.granite.util.Introspector;
053import org.granite.util.PropertyDescriptor;
054import org.granite.util.TypeUtil;
055import org.granite.util.TypeUtil.DeclaredAnnotation;
056import org.granite.util.XMap;
057
058/**
059 * @author Franck WOLFF
060 */
061public 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(ExternalizedProperty.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(IgnoredProperty.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<ExternalizedProperty> annotation = TypeUtil.getAnnotation(getter, ExternalizedProperty.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
322interface DefaultConstructorFactory {
323    public <T> Constructor<T> findDefaultConstructor(Class<T> clazz);
324}
325
326class 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
333class 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}