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 }