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