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.eclipselink; 023 024import java.io.IOException; 025import java.io.ObjectInput; 026import java.io.ObjectOutput; 027import java.lang.reflect.Field; 028import java.lang.reflect.InvocationTargetException; 029import java.lang.reflect.ParameterizedType; 030import java.lang.reflect.Type; 031import java.util.ArrayList; 032import java.util.HashMap; 033import java.util.List; 034import java.util.Map; 035import java.util.Set; 036 037import javax.persistence.Embeddable; 038import javax.persistence.Entity; 039import javax.persistence.IdClass; 040import javax.persistence.MappedSuperclass; 041 042import org.eclipse.persistence.indirection.IndirectContainer; 043import org.eclipse.persistence.indirection.IndirectList; 044import org.eclipse.persistence.indirection.IndirectMap; 045import org.eclipse.persistence.indirection.IndirectSet; 046import org.eclipse.persistence.indirection.ValueHolderInterface; 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 EclipseLinkExternalizer extends DefaultExternalizer { 067 068 private static final Logger log = Logger.getLogger(EclipseLinkExternalizer.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 EclipseLink pseudo-proxy should be null or IdClass (" + type + ")"); 095 096 return new EclipseLinkValueHolder(); 097 } 098 099 @Override 100 public void readExternal(Object o, ObjectInput in) throws IOException, ClassNotFoundException, IllegalAccessException { 101 // Ignore EclipseLink proxies 102 if (o instanceof EclipseLinkValueHolder) 103 return; 104 105 // @Embeddable or others... 106 if (!isRegularEntity(o.getClass()) && !isEmbeddable(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("_persistence_" + 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 @Override 148 protected boolean isPropertyIgnored(Field field) { 149 return field.getName().equals("_persistence_fetchGroup"); 150 } 151 152 153 protected IndirectContainer newIndirectCollection(AbstractExternalizablePersistentCollection value, Type target) { 154 final boolean initialized = value.isInitialized(); 155 final Object[] content = value.getContent(); 156 157 IndirectContainer coll = null; 158 if (value instanceof ExternalizablePersistentSet) { 159 if (initialized) { 160 if (content != null) { 161 Set<?> set = ((ExternalizablePersistentSet)value).getContentAsSet(target); 162 coll = new IndirectSet(set); 163 } 164 } 165 else 166 coll = new IndirectSet(); 167 } 168 else if (value instanceof ExternalizablePersistentList) { 169 if (initialized) { 170 if (content != null) { 171 List<?> list = ((ExternalizablePersistentList)value).getContentAsList(target); 172 coll = new IndirectList(list); 173 } 174 } 175 else 176 coll = new IndirectList(); 177 } 178 else if (value instanceof ExternalizablePersistentMap) { 179 if (initialized) { 180 if (content != null) { 181 Map<?, ?> map = ((ExternalizablePersistentMap)value).getContentAsMap(target); 182 coll = new IndirectMap(map); 183 } 184 } 185 else 186 coll = new IndirectMap(); 187 } 188 else { 189 throw new RuntimeException("Illegal externalizable persitent class: " + value); 190 } 191 192 return coll; 193 } 194 195 196 @Override 197 public void writeExternal(Object o, ObjectOutput out) throws IOException, IllegalAccessException { 198 199 ClassGetter classGetter = GraniteContext.getCurrentInstance().getGraniteConfig().getClassGetter(); 200 Class<?> oClass = classGetter.getClass(o); 201 202 if (o instanceof EclipseLinkProxy) { 203 EclipseLinkProxy proxy = (EclipseLinkProxy)o; 204 205 // Only write initialized flag, null detachedState & null id if proxy is uninitialized. 206 String description = proxy.getProxiedClass().getName(); 207 log.debug("Writing uninitialized EclipseLink ValueHolder %s", description); 208 209 // Write initialized flag. 210 out.writeObject(Boolean.FALSE); 211 // Write detachedState. 212 out.writeObject(null); 213 // Write id. 214 out.writeObject(null); 215 216 return; 217 } 218 219 if (!isRegularEntity(o.getClass()) && !isEmbeddable(o.getClass())) { // @Embeddable or others... 220 log.debug("Delegating non regular entity writing to DefaultExternalizer..."); 221 super.writeExternal(o, out); 222 } 223 else { 224 if (isRegularEntity(o.getClass())) { 225 // Write initialized flag. 226 out.writeObject(Boolean.TRUE); 227 // Write detachedState. 228 out.writeObject(null); 229 } 230 231 // Externalize entity fields. 232 List<Property> fields = findOrderedFields(oClass); 233 List<String> lazyFieldNames = new ArrayList<String>(); 234 235 log.debug("Writing entity %s with fields %s", o.getClass().getName(), fields); 236 for (Property field : fields) { 237 if (!(field.getType() instanceof Class<?> && ValueHolderInterface.class.isAssignableFrom((Class<?>)field.getType()))) { 238 if (lazyFieldNames.contains(field.getName())) { 239 EclipseLinkProxy proxy = new EclipseLinkProxy((Class<?>)field.getType()); 240 out.writeObject(proxy); 241 } 242 else { 243 Object value = field.getProperty(o); 244 245 // Persistent collections & maps. 246 if (value instanceof IndirectContainer) 247 value = newExternalizableCollection((IndirectContainer)value); 248 // Transient maps. 249 else if (value instanceof Map<?, ?>) 250 value = BasicMap.newInstance((Map<?, ?>)value); 251 252 out.writeObject(value); 253 } 254 } 255 else { 256 ValueHolderInterface vh = (ValueHolderInterface)field.getProperty(o); 257 if (vh != null && !vh.isInstantiated()) 258 lazyFieldNames.add(field.getName().substring("_persistence_".length(), field.getName().length()-3)); 259 } 260 } 261 } 262 } 263 264 protected AbstractExternalizablePersistentCollection newExternalizableCollection(IndirectContainer value) { 265 AbstractExternalizablePersistentCollection coll = null; 266 boolean initialized = value.isInstantiated(); 267 268 if (value instanceof IndirectSet) { 269 coll = new ExternalizablePersistentSet(initialized ? ((IndirectSet)value).toArray() : null, initialized, false); 270 } 271 else if (value instanceof IndirectList) { 272 coll = new ExternalizablePersistentList(initialized ? ((IndirectList)value).toArray() : null, initialized, false); 273 } 274 else if (value instanceof IndirectMap) { 275 Object[] content = null; 276 277 if (initialized) { 278 content = new Object[((IndirectMap)value).size()]; 279 int index = 0; 280 @SuppressWarnings("unchecked") 281 Set<Map.Entry<?, ?>> entries = ((IndirectMap)value).entrySet(); 282 for (Map.Entry<?, ?> entry : entries) 283 content[index++] = new Object[]{entry.getKey(), entry.getValue()}; 284 } 285 286 coll = new ExternalizablePersistentMap(content, initialized, false); 287 } 288 else { 289 throw new UnsupportedOperationException("Unsupported EclipseLink collection type: " + value); 290 } 291 292 return coll; 293 } 294 295 296 @Override 297 public int accept(Class<?> clazz) { 298 return ( 299 clazz.isAnnotationPresent(Entity.class) || 300 clazz.isAnnotationPresent(MappedSuperclass.class) || 301 clazz.isAnnotationPresent(Embeddable.class) 302 ) ? 1 : -1; 303 } 304 305 protected boolean isRegularEntity(Class<?> clazz) { 306 return clazz.isAnnotationPresent(Entity.class) || clazz.isAnnotationPresent(MappedSuperclass.class); 307 } 308 309 protected boolean isEmbeddable(Class<?> clazz) { 310 return clazz.isAnnotationPresent(Embeddable.class); 311 } 312}