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