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