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