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.toplink; 023 024import java.io.IOException; 025import java.io.ObjectInput; 026import java.io.ObjectOutput; 027import java.lang.reflect.InvocationTargetException; 028import java.lang.reflect.ParameterizedType; 029import java.lang.reflect.Type; 030import java.util.ArrayList; 031import java.util.HashMap; 032import java.util.List; 033import java.util.Map; 034import java.util.Set; 035 036import javax.persistence.Embeddable; 037import javax.persistence.Entity; 038import javax.persistence.IdClass; 039import javax.persistence.MappedSuperclass; 040 041import oracle.toplink.essentials.indirection.IndirectContainer; 042import oracle.toplink.essentials.indirection.IndirectList; 043import oracle.toplink.essentials.indirection.IndirectMap; 044import oracle.toplink.essentials.indirection.IndirectSet; 045import oracle.toplink.essentials.indirection.ValueHolderInterface; 046 047import org.granite.collections.BasicMap; 048import org.granite.config.GraniteConfig; 049import org.granite.context.GraniteContext; 050import org.granite.logging.Logger; 051import org.granite.messaging.amf.io.convert.Converters; 052import org.granite.messaging.amf.io.util.ClassGetter; 053import org.granite.messaging.amf.io.util.MethodProperty; 054import org.granite.messaging.amf.io.util.Property; 055import org.granite.messaging.amf.io.util.externalizer.DefaultExternalizer; 056import org.granite.messaging.annotations.Include; 057import org.granite.messaging.persistence.AbstractExternalizablePersistentCollection; 058import org.granite.messaging.persistence.ExternalizablePersistentList; 059import org.granite.messaging.persistence.ExternalizablePersistentMap; 060import org.granite.messaging.persistence.ExternalizablePersistentSet; 061import org.granite.util.TypeUtil; 062 063/** 064 * @author William DRAI 065 */ 066public class TopLinkExternalizer extends DefaultExternalizer { 067 068 private static final Logger log = Logger.getLogger(TopLinkExternalizer.class); 069 070 @Override 071 public Object newInstance(String type, ObjectInput in) 072 throws IOException, ClassNotFoundException, InstantiationException, InvocationTargetException, IllegalAccessException { 073 074 // If type is not an entity (@Embeddable for example), we don't read initialized/detachedState 075 // and we fall back to DefaultExternalizer behavior. 076 Class<?> clazz = TypeUtil.forName(type); 077 if (!isRegularEntity(clazz)) 078 return super.newInstance(type, in); 079 080 // Read initialized flag. 081 boolean initialized = ((Boolean)in.readObject()).booleanValue(); 082 083 // Read detached state. 084 @SuppressWarnings("unused") 085 String detachedState = (String)in.readObject(); 086 087 // New or initialized entity. 088 if (initialized) 089 return super.newInstance(type, in); 090 091 // Pseudo-proxy (uninitialized entity). 092 Object id = in.readObject(); 093 if (id != null && (!clazz.isAnnotationPresent(IdClass.class) || !clazz.getAnnotation(IdClass.class).value().equals(id.getClass()))) 094 throw new RuntimeException("Id for TopLink pseudo-proxy should be null or IdClass (" + type + ")"); 095 096 return new TopLinkValueHolder(); 097 } 098 099 @Override 100 public void readExternal(Object o, ObjectInput in) throws IOException, ClassNotFoundException, IllegalAccessException { 101 // Ignore TopLink proxies 102 if (o instanceof TopLinkValueHolder) 103 return; 104 105 // @Embeddable or others... 106 if (!isRegularEntity(o.getClass())) { 107 log.debug("Delegating non regular entity reading to DefaultExternalizer..."); 108 super.readExternal(o, in); 109 } 110 // Regural @Entity or @MappedSuperclass 111 else { 112 GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig(); 113 114 Converters converters = config.getConverters(); 115 ClassGetter classGetter = config.getClassGetter(); 116 Class<?> oClass = classGetter.getClass(o); 117 ParameterizedType[] declaringTypes = TypeUtil.getDeclaringTypes(oClass); 118 119 List<Property> fields = findOrderedFields(oClass); 120 log.debug("Reading entity %s with fields %s", oClass.getName(), fields); 121 Map<String, Property> topLinkFields = new HashMap<String, Property>(); 122 for (Property field : fields) { 123 if (field.getType() instanceof Class<?> && ValueHolderInterface.class.isAssignableFrom((Class<?>)field.getType())) { 124 topLinkFields.put(field.getName(), field); 125 } 126 else { 127 Object value = in.readObject(); 128 129 if (value instanceof ValueHolderInterface) { 130 topLinkFields.get("_toplink_" + field.getName() + "_vh").setProperty(o, value, false); 131 } 132 else if (!(field instanceof MethodProperty && field.isAnnotationPresent(Include.class, true))) { 133 if (value instanceof AbstractExternalizablePersistentCollection) 134 value = newIndirectCollection((AbstractExternalizablePersistentCollection)value, field.getType()); 135 else { 136 Type targetType = TypeUtil.resolveTypeVariable(field.getType(), field.getDeclaringClass(), declaringTypes); 137 value = converters.convert(value, targetType); 138 } 139 140 field.setProperty(o, value, false); 141 } 142 } 143 } 144 } 145 } 146 147 protected IndirectContainer newIndirectCollection(AbstractExternalizablePersistentCollection value, Type target) { 148 final boolean initialized = value.isInitialized(); 149 final Object[] content = value.getContent(); 150 151 IndirectContainer coll = null; 152 if (value instanceof ExternalizablePersistentSet) { 153 if (initialized) { 154 if (content != null) { 155 Set<?> set = ((ExternalizablePersistentSet)value).getContentAsSet(target); 156 coll = new IndirectSet(set); 157 } 158 } 159 else 160 coll = new IndirectSet(); 161 } 162 else if (value instanceof ExternalizablePersistentList) { 163 if (initialized) { 164 if (content != null) { 165 List<?> list = ((ExternalizablePersistentList)value).getContentAsList(target); 166 coll = new IndirectList(list); 167 } 168 } 169 else 170 coll = new IndirectList(); 171 } 172 else if (value instanceof ExternalizablePersistentMap) { 173 if (initialized) { 174 if (content != null) { 175 Map<?, ?> map = ((ExternalizablePersistentMap)value).getContentAsMap(target); 176 coll = new IndirectMap(map); 177 } 178 } 179 else 180 coll = new IndirectMap(); 181 } 182 else { 183 throw new RuntimeException("Illegal externalizable persitent class: " + value); 184 } 185 186 return coll; 187 } 188 189 190 @Override 191 public void writeExternal(Object o, ObjectOutput out) throws IOException, IllegalAccessException { 192 193 ClassGetter classGetter = GraniteContext.getCurrentInstance().getGraniteConfig().getClassGetter(); 194 Class<?> oClass = classGetter.getClass(o); 195 196 if (o instanceof TopLinkProxy) { 197 TopLinkProxy proxy = (TopLinkProxy)o; 198 199 // Only write initialized flag, null detachedState & null id if proxy is uninitialized. 200 log.debug("Writing uninitialized TopLink ValueHolder %s", proxy.getProxiedClass().getName()); 201 202 // Write initialized flag. 203 out.writeObject(Boolean.FALSE); 204 // Write detachedState. 205 out.writeObject(null); 206 // Write id. 207 out.writeObject(null); 208 209 return; 210 } 211 212 if (!isRegularEntity(o.getClass())) { // @Embeddable or others... 213 log.debug("Delegating non regular entity writing to DefaultExternalizer..."); 214 super.writeExternal(o, out); 215 } 216 else { 217 // Write initialized flag. 218 out.writeObject(Boolean.TRUE); 219 // Write detachedState. 220 out.writeObject(null); 221 222 // Externalize entity fields. 223 List<Property> fields = findOrderedFields(oClass); 224 List<String> lazyFieldNames = new ArrayList<String>(); 225 226 log.debug("Writing entity %s with fields %s", o.getClass().getName(), fields); 227 for (Property field : fields) { 228 if (!(field.getType() instanceof Class<?> && ValueHolderInterface.class.isAssignableFrom((Class<?>)field.getType()))) { 229 if (lazyFieldNames.contains(field.getName())) { 230 TopLinkProxy proxy = new TopLinkProxy((Class<?>)field.getType()); 231 out.writeObject(proxy); 232 } 233 else { 234 Object value = field.getProperty(o); 235 236 // Persistent collections & maps. 237 if (value instanceof IndirectContainer) 238 value = newExternalizableCollection((IndirectContainer)value); 239 // Transient maps. 240 else if (value instanceof Map<?, ?>) 241 value = BasicMap.newInstance((Map<?, ?>)value); 242 243 out.writeObject(value); 244 } 245 } 246 else { 247 ValueHolderInterface vh = (ValueHolderInterface)field.getProperty(o); 248 if (!vh.isInstantiated()) 249 lazyFieldNames.add(field.getName().substring("_toplink_".length(), field.getName().length()-3)); 250 } 251 } 252 } 253 } 254 255 protected AbstractExternalizablePersistentCollection newExternalizableCollection(IndirectContainer value) { 256 AbstractExternalizablePersistentCollection coll = null; 257 boolean initialized = value.isInstantiated(); 258 259 if (value instanceof IndirectSet) { 260 coll = new ExternalizablePersistentSet(initialized ? ((IndirectSet)value).toArray() : null, initialized, false); 261 } 262 else if (value instanceof IndirectList) { 263 coll = new ExternalizablePersistentList(initialized ? ((IndirectList)value).toArray() : null, initialized, false); 264 } 265 else if (value instanceof IndirectMap) { 266 Object[] content = null; 267 268 if (initialized) { 269 content = new Object[((IndirectMap)value).size()]; 270 int index = 0; 271 @SuppressWarnings("unchecked") 272 Set<Map.Entry<?, ?>> entries = ((IndirectMap)value).entrySet(); 273 for (Map.Entry<?, ?> entry : entries) 274 content[index++] = new Object[]{entry.getKey(), entry.getValue()}; 275 } 276 277 coll = new ExternalizablePersistentMap(content, initialized, false); 278 } 279 else { 280 throw new UnsupportedOperationException("Unsupported TopLink collection type: " + value); 281 } 282 283 return coll; 284 } 285 286 287 @Override 288 public int accept(Class<?> clazz) { 289 return ( 290 clazz.isAnnotationPresent(Entity.class) || 291 clazz.isAnnotationPresent(MappedSuperclass.class) || 292 clazz.isAnnotationPresent(Embeddable.class) 293 ) ? 1 : -1; 294 } 295 296 protected boolean isRegularEntity(Class<?> clazz) { 297 return clazz.isAnnotationPresent(Entity.class) || clazz.isAnnotationPresent(MappedSuperclass.class); 298 } 299}