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