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 import org.granite.util.ClassUtil.DeclaredAnnotation;
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 = ClassUtil.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<?>)ClassUtil.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 = ClassUtil.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 = ClassUtil.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 <T> Constructor<T> findDefaultConstructor(Class<T> clazz) {
254 Constructor<T> constructor = null;
255
256 GraniteContext context = GraniteContext.getCurrentInstance();
257 String instantiator = context.getGraniteConfig().getInstantiator(clazz.getName());
258 if (instantiator != null) {
259 try {
260 Class<T> instantiatorClass = ClassUtil.forName(instantiator, clazz);
261 constructor = instantiatorClass.getConstructor();
262 } catch (ClassNotFoundException e) {
263 throw new RuntimeException(
264 "Could not load instantiator class: " + instantiator + " for: " + clazz.getName(), e
265 );
266 } catch (NoSuchMethodException e) {
267 throw new RuntimeException(
268 "Could not find default constructor in instantiator class: " + instantiator, e
269 );
270 }
271 }
272 else {
273 try {
274 constructor = clazz.getConstructor();
275 } catch (NoSuchMethodException e) {
276 // fall down...
277 }
278
279 if (constructor == null) {
280 String key = DefaultConstructorFactory.class.getName();
281 DefaultConstructorFactory factory = getDefaultConstructorFactory(context, key);
282 constructor = factory.findDefaultConstructor(clazz);
283 }
284 }
285
286 return constructor;
287 }
288
289 private DefaultConstructorFactory getDefaultConstructorFactory(
290 GraniteContext context,
291 String key) {
292
293 lock.lock();
294 try {
295 DefaultConstructorFactory factory =
296 (DefaultConstructorFactory)context.getApplicationMap().get(key);
297 if (factory == null) {
298 try {
299 factory = new SunDefaultConstructorFactory();
300 } catch (Exception e) {
301 // fall down...
302 }
303 if (factory == null)
304 factory = new NoDefaultConstructorFactory();
305 context.getApplicationMap().put(key, factory);
306 }
307 return factory;
308 } finally {
309 lock.unlock();
310 }
311 }
312
313 public int accept(Class<?> clazz) {
314 return clazz.isAnnotationPresent(ExternalizedBean.class) ? 0 : -1;
315 }
316 }
317
318 interface DefaultConstructorFactory {
319 public <T> Constructor<T> findDefaultConstructor(Class<T> clazz);
320 }
321
322 class NoDefaultConstructorFactory implements DefaultConstructorFactory {
323
324 public <T> Constructor<T> findDefaultConstructor(Class<T> clazz) {
325 throw new RuntimeException("Could not find default constructor in class: " + clazz);
326 }
327 }
328
329 class SunDefaultConstructorFactory implements DefaultConstructorFactory {
330
331 private final Object reflectionFactory;
332 private final Method newConstructorForSerialization;
333
334 public SunDefaultConstructorFactory() {
335 try {
336 Class<?> factoryClass = ClassUtil.forName("sun.reflect.ReflectionFactory");
337 Method getReflectionFactory = factoryClass.getDeclaredMethod("getReflectionFactory");
338 reflectionFactory = getReflectionFactory.invoke(null);
339 newConstructorForSerialization = factoryClass.getDeclaredMethod(
340 "newConstructorForSerialization",
341 new Class[]{Class.class, Constructor.class}
342 );
343 } catch (Exception e) {
344 throw new RuntimeException("Could not create Sun Factory", e);
345 }
346 }
347
348 @SuppressWarnings("unchecked")
349 public <T> Constructor<T> findDefaultConstructor(Class<T> clazz) {
350 try {
351 Constructor<?> constructor = Object.class.getDeclaredConstructor();
352 constructor = (Constructor<?>)newConstructorForSerialization.invoke(
353 reflectionFactory,
354 new Object[]{clazz, constructor}
355 );
356 constructor.setAccessible(true);
357 return (Constructor<T>)constructor;
358 } catch (Exception e) {
359 throw new RuntimeException(e);
360 }
361 }
362 }