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.openjpa; 023 024import java.io.ByteArrayInputStream; 025import java.io.ByteArrayOutputStream; 026import java.io.IOException; 027import java.io.ObjectInput; 028import java.io.ObjectInputStream; 029import java.io.ObjectOutput; 030import java.io.ObjectOutputStream; 031import java.lang.reflect.Field; 032import java.lang.reflect.InvocationTargetException; 033import java.lang.reflect.ParameterizedType; 034import java.lang.reflect.Type; 035import java.util.ArrayList; 036import java.util.Arrays; 037import java.util.BitSet; 038import java.util.Collection; 039import java.util.HashMap; 040import java.util.List; 041import java.util.Map; 042import java.util.Set; 043 044import javax.persistence.Embeddable; 045import javax.persistence.Entity; 046import javax.persistence.IdClass; 047import javax.persistence.MappedSuperclass; 048 049import org.apache.openjpa.enhance.PersistenceCapable; 050import org.apache.openjpa.kernel.OpenJPAStateManager; 051import org.apache.openjpa.util.ProxyCollection; 052import org.apache.openjpa.util.ProxyMap; 053import org.granite.collections.BasicMap; 054import org.granite.config.GraniteConfig; 055import org.granite.context.GraniteContext; 056import org.granite.logging.Logger; 057import org.granite.messaging.amf.io.convert.Converters; 058import org.granite.messaging.amf.io.util.ClassGetter; 059import org.granite.messaging.amf.io.util.MethodProperty; 060import org.granite.messaging.amf.io.util.Property; 061import org.granite.messaging.amf.io.util.externalizer.DefaultExternalizer; 062import org.granite.messaging.annotations.Include; 063import org.granite.messaging.persistence.AbstractExternalizablePersistentCollection; 064import org.granite.messaging.persistence.ExternalizablePersistentList; 065import org.granite.messaging.persistence.ExternalizablePersistentMap; 066import org.granite.messaging.persistence.ExternalizablePersistentSet; 067import org.granite.util.StringUtil; 068import org.granite.util.TypeUtil; 069 070/** 071 * @author Franck WOLFF 072 */ 073public class OpenJpaExternalizer extends DefaultExternalizer { 074 075 private static final Logger log = Logger.getLogger(OpenJpaExternalizer.class); 076 077 @Override 078 public Object newInstance(String type, ObjectInput in) 079 throws IOException, ClassNotFoundException, InstantiationException, InvocationTargetException, IllegalAccessException { 080 081 // If type is not an entity (@Embeddable for example), we don't read initialized/detachedState 082 // and we fall back to DefaultExternalizer behavior. 083 Class<?> clazz = TypeUtil.forName(type); 084 if (!isRegularEntity(clazz)) 085 return super.newInstance(type, in); 086 087 // Read initialized flag. 088 boolean initialized = ((Boolean)in.readObject()).booleanValue(); 089 090 // Read detached state... 091 String detachedState = (String)in.readObject(); 092 093 // Pseudo-proxy (uninitialized entity). 094 if (!initialized) { 095 Object id = in.readObject(); 096 if (id != null && (!clazz.isAnnotationPresent(IdClass.class) || !clazz.getAnnotation(IdClass.class).value().equals(id.getClass()))) 097 throw new RuntimeException("Id for OpenJPA pseudo-proxy should be null or IdClass (" + type + ")"); 098 return null; 099 } 100 101 // New entity. 102 if (detachedState == null) 103 return super.newInstance(type, in); 104 105 // Existing entity. 106 Object entity = clazz.newInstance(); 107 if (detachedState.length() > 0) { 108 byte[] data = StringUtil.hexStringToBytes(detachedState); 109 ((PersistenceCapable)entity).pcSetDetachedState(deserializeDetachedState(data)); 110 } 111 return entity; 112 } 113 114 @Override 115 public void readExternal(Object o, ObjectInput in) throws IOException, ClassNotFoundException, IllegalAccessException { 116 117 if (!isRegularEntity(o.getClass()) && !isEmbeddable(o.getClass())) { 118 log.debug("Delegating non regular entity reading to DefaultExternalizer..."); 119 super.readExternal(o, in); 120 } 121 // Regular @Entity or @MappedSuperclass 122 else { 123 GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig(); 124 125 Converters converters = config.getConverters(); 126 ClassGetter classGetter = config.getClassGetter(); 127 Class<?> oClass = classGetter.getClass(o); 128 ParameterizedType[] declaringTypes = TypeUtil.getDeclaringTypes(oClass); 129 130 List<Property> fields = findOrderedFields(oClass); 131 log.debug("Reading entity %s with fields %s", oClass.getName(), fields); 132 for (Property field : fields) { 133 Object value = in.readObject(); 134 135 if (!(field instanceof MethodProperty && field.isAnnotationPresent(Include.class, true))) { 136 137 // (Un)Initialized collections/maps. 138 if (value instanceof AbstractExternalizablePersistentCollection) { 139 // Uninitialized. 140 if (!((AbstractExternalizablePersistentCollection)value).isInitialized()) 141 value = null; 142 // Initialized. 143 else { 144 if (value instanceof ExternalizablePersistentSet) 145 value = ((ExternalizablePersistentSet)value).getContentAsSet(field.getType()); 146 else if (value instanceof ExternalizablePersistentMap) 147 value = ((ExternalizablePersistentMap)value).getContentAsMap(field.getType()); 148 else 149 value = ((ExternalizablePersistentList)value).getContentAsList(field.getType()); 150 } 151 } 152 // Others... 153 else { 154 Type targetType = TypeUtil.resolveTypeVariable(field.getType(), field.getDeclaringClass(), declaringTypes); 155 value = converters.convert(value, targetType); 156 } 157 158 field.setProperty(o, value, false); 159 } 160 } 161 } 162 } 163 164 @Override 165 public void writeExternal(Object o, ObjectOutput out) throws IOException, IllegalAccessException { 166 167 ClassGetter classGetter = GraniteContext.getCurrentInstance().getGraniteConfig().getClassGetter(); 168 Class<?> oClass = classGetter.getClass(o); 169 170 if (!isRegularEntity(o.getClass()) && !isEmbeddable(o.getClass())) { // @Embeddable or others... 171 log.debug("Delegating non regular entity writing to DefaultExternalizer..."); 172 super.writeExternal(o, out); 173 } 174 else { 175 PersistenceCapable pco = (PersistenceCapable)o; 176 177 if (isRegularEntity(o.getClass())) { 178 // Pseudo-proxy created for uninitialized entities (see below). 179 if (Boolean.FALSE.equals(pco.pcGetDetachedState())) { 180 // Write uninitialized flag. 181 out.writeObject(Boolean.FALSE); 182 // Write detached state. 183 out.writeObject(null); 184 // Write id. 185 out.writeObject(null); 186 return; 187 } 188 189 // Write initialized flag. 190 out.writeObject(Boolean.TRUE); 191 192 // Write detached state as a String, in the form of an hex representation 193 // of the serialized detached state. 194 byte[] detachedState = serializeDetachedState(pco); 195 char[] hexDetachedState = StringUtil.bytesToHexChars(detachedState); 196 out.writeObject(new String(hexDetachedState)); 197 } 198 199 // Externalize entity fields. 200 List<Property> fields = findOrderedFields(oClass); 201 Map<String, Boolean> loadedState = getLoadedState(pco, oClass); 202 log.debug("Writing entity %s with fields %s", o.getClass().getName(), fields); 203 for (Property field : fields) { 204 Object value = field.getProperty(o); 205 206 // Uninitialized associations. 207 if (value == null && loadedState.containsKey(field.getName()) && !loadedState.get(field.getName())) { 208 Class<?> fieldClass = TypeUtil.classOfType(field.getType()); 209 210 // Create a "pseudo-proxy" for uninitialized entities: detached state is set to 211 // Boolean.FALSE (uninitialized flag). 212 if (PersistenceCapable.class.isAssignableFrom(fieldClass)) { 213 try { 214 value = fieldClass.newInstance(); 215 } catch (Exception e) { 216 throw new RuntimeException("Could not create OpenJPA pseudo-proxy for: " + field, e); 217 } 218 ((PersistenceCapable)value).pcSetDetachedState(Boolean.FALSE); 219 } 220 // Create pseudo-proxy for collections (set or list). 221 else if (Collection.class.isAssignableFrom(fieldClass)) { 222 if (Set.class.isAssignableFrom(fieldClass)) 223 value = new ExternalizablePersistentSet((Object[])null, false, false); 224 else 225 value = new ExternalizablePersistentList((Object[])null, false, false); 226 } 227 // Create pseudo-proxy for maps. 228 else if (Map.class.isAssignableFrom(fieldClass)) { 229 value = new ExternalizablePersistentMap((Object[])null, false, false); 230 } 231 } 232 233 // Initialized collections. 234 else if (value instanceof ProxyCollection) { 235 if (value instanceof Set<?>) 236 value = new ExternalizablePersistentSet(((ProxyCollection)value).toArray(), true, false); 237 else 238 value = new ExternalizablePersistentList(((ProxyCollection)value).toArray(), true, false); 239 } 240 241 // Initialized maps. 242 else if (value instanceof ProxyMap) { 243 value = new ExternalizablePersistentMap((Object[])null, true, false); 244 ((ExternalizablePersistentMap)value).setContentFromMap((ProxyMap)value); 245 } 246 247 // Transient maps. 248 else if (value instanceof Map<?, ?>) 249 value = BasicMap.newInstance((Map<?, ?>)value); 250 251 out.writeObject(value); 252 } 253 } 254 } 255 256 @Override 257 public int accept(Class<?> clazz) { 258 return ( 259 clazz.isAnnotationPresent(Entity.class) || 260 clazz.isAnnotationPresent(MappedSuperclass.class) || 261 clazz.isAnnotationPresent(Embeddable.class) 262 ) ? 1 : -1; 263 } 264 265 protected boolean isRegularEntity(Class<?> clazz) { 266 return PersistenceCapable.class.isAssignableFrom(clazz) && ( 267 clazz.isAnnotationPresent(Entity.class) || clazz.isAnnotationPresent(MappedSuperclass.class) 268 ); 269 } 270 271 protected boolean isEmbeddable(Class<?> clazz) { 272 return PersistenceCapable.class.isAssignableFrom(clazz) && clazz.isAnnotationPresent(Embeddable.class); 273 } 274 275 // Very hacky! 276 static Map<String, Boolean> getLoadedState(PersistenceCapable pc, Class<?> clazz) { 277 try { 278 BitSet loaded = null; 279 if (pc.pcGetStateManager() instanceof OpenJPAStateManager) { 280 OpenJPAStateManager sm = (OpenJPAStateManager)pc.pcGetStateManager(); 281 loaded = sm.getLoaded(); 282 } 283 // State manager may be null for some entities... 284 if (loaded == null) { 285 Object ds = pc.pcGetDetachedState(); 286 if (ds != null && ds.getClass().isArray()) { 287 Object[] dsa = (Object[])ds; 288 if (dsa.length > 1 && dsa[1] instanceof BitSet) 289 loaded = (BitSet)dsa[1]; 290 } 291 } 292 293 List<String> fieldNames = new ArrayList<String>(); 294 for (Class<?> c = clazz; c != null && PersistenceCapable.class.isAssignableFrom(c); c = c.getSuperclass()) { 295 Field pcFieldNames = c.getDeclaredField("pcFieldNames"); 296 pcFieldNames.setAccessible(true); 297 fieldNames.addAll(0, Arrays.asList((String[])pcFieldNames.get(null))); 298 } 299 300 Map<String, Boolean> loadedState = new HashMap<String, Boolean>(); 301 for (int i = 0; i < fieldNames.size(); i++) 302 loadedState.put(fieldNames.get(i), (loaded != null && loaded.size() > i ? loaded.get(i) : true)); 303 return loadedState; 304 } 305 catch (Exception e) { 306 throw new RuntimeException("Could not get loaded state for: " + pc); 307 } 308 } 309 310 protected byte[] serializeDetachedState(PersistenceCapable pc) { 311 try { 312 ByteArrayOutputStream baos = new ByteArrayOutputStream(256); 313 ObjectOutputStream oos = new ObjectOutputStream(baos); 314 oos.writeObject(pc.pcGetDetachedState()); 315 return baos.toByteArray(); 316 } catch (Exception e) { 317 throw new RuntimeException("Could not serialize detached state for: " + pc); 318 } 319 } 320 321 protected Object deserializeDetachedState(byte[] data) { 322 try { 323 ByteArrayInputStream baos = new ByteArrayInputStream(data); 324 ObjectInputStream oos = new ObjectInputStream(baos); 325 return oos.readObject(); 326 } catch (Exception e) { 327 throw new RuntimeException("Could not deserialize detached state for: " + data); 328 } 329 } 330}