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
021 package org.granite.datanucleus;
022
023 import java.io.ByteArrayInputStream;
024 import java.io.ByteArrayOutputStream;
025 import java.io.IOException;
026 import java.io.ObjectInput;
027 import java.io.ObjectInputStream;
028 import java.io.ObjectOutput;
029 import java.io.ObjectOutputStream;
030 import java.lang.reflect.Field;
031 import java.lang.reflect.InvocationTargetException;
032 import java.lang.reflect.Type;
033 import java.util.ArrayList;
034 import java.util.Arrays;
035 import java.util.BitSet;
036 import java.util.Collection;
037 import java.util.HashMap;
038 import java.util.HashSet;
039 import java.util.Iterator;
040 import java.util.List;
041 import java.util.Map;
042 import java.util.Set;
043 import java.util.SortedMap;
044 import java.util.SortedSet;
045 import java.util.TreeMap;
046 import java.util.TreeSet;
047
048 import javax.jdo.spi.Detachable;
049 import javax.jdo.spi.PersistenceCapable;
050 import javax.jdo.spi.StateManager;
051 import javax.persistence.Embeddable;
052 import javax.persistence.Entity;
053 import javax.persistence.IdClass;
054 import javax.persistence.MappedSuperclass;
055
056 import org.granite.config.GraniteConfig;
057 import org.granite.context.GraniteContext;
058 import org.granite.logging.Logger;
059 import org.granite.messaging.amf.io.convert.Converters;
060 import org.granite.messaging.amf.io.util.ClassGetter;
061 import org.granite.messaging.amf.io.util.MethodProperty;
062 import org.granite.messaging.amf.io.util.Property;
063 import org.granite.messaging.amf.io.util.externalizer.DefaultExternalizer;
064 import org.granite.messaging.amf.io.util.externalizer.annotation.ExternalizedProperty;
065 import org.granite.messaging.persistence.AbstractExternalizablePersistentCollection;
066 import org.granite.messaging.persistence.ExternalizablePersistentList;
067 import org.granite.messaging.persistence.ExternalizablePersistentMap;
068 import org.granite.messaging.persistence.ExternalizablePersistentSet;
069 import org.granite.util.ClassUtil;
070 import org.granite.util.StringUtil;
071
072
073 /**
074 * @author Stephen MORE
075 * @author William DRAI
076 */
077 public class DataNucleusExternalizer extends DefaultExternalizer {
078
079 private static final Logger log = Logger.getLogger(DataNucleusExternalizer.class);
080
081 private static final Integer NULL_ID = Integer.valueOf(0);
082
083
084 @Override
085 public Object newInstance(String type, ObjectInput in)
086 throws IOException, ClassNotFoundException, InstantiationException, InvocationTargetException, IllegalAccessException {
087
088 // If type is not an entity (@Embeddable for example), we don't read initialized/detachedState
089 // and we fall back to DefaultExternalizer behavior.
090 Class<?> clazz = ClassUtil.forName(type);
091 if (!isRegularEntity(clazz))
092 return super.newInstance(type, in);
093
094 // Read initialized flag.
095 boolean initialized = ((Boolean)in.readObject()).booleanValue();
096
097 // Read detachedState.
098 String detachedState = (String)in.readObject();
099
100 // New entity.
101 if (initialized && detachedState == null)
102 return super.newInstance(type, in);
103
104 // Pseudo-proxy (uninitialized entity).
105 if (!initialized) {
106 Object id = in.readObject();
107 if (id != null && (!clazz.isAnnotationPresent(IdClass.class) || !clazz.getAnnotation(IdClass.class).value().equals(id.getClass())))
108 throw new RuntimeException("Id for DataNucleus pseudo-proxy should be null (" + type + ")");
109 return null;
110 }
111
112 // Existing entity.
113 Object entity = clazz.newInstance();
114 if (detachedState.length() > 0) {
115 byte[] data = StringUtil.hexStringToBytes(detachedState);
116 deserializeDetachedState((Detachable)entity, data);
117 }
118 return entity;
119 }
120
121 @Override
122 public void readExternal(Object o, ObjectInput in) throws IOException, ClassNotFoundException, IllegalAccessException {
123
124 if (!isRegularEntity(o.getClass())) {
125 log.debug("Delegating non regular entity reading to DefaultExternalizer...");
126 super.readExternal(o, in);
127 }
128 // Regular @Entity or @MappedSuperclass
129 else {
130 GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
131
132 Converters converters = config.getConverters();
133 ClassGetter classGetter = config.getClassGetter();
134 Class<?> oClass = classGetter.getClass(o);
135 Object[] detachedState = getDetachedState((Detachable)o);
136
137 List<Property> fields = findOrderedFields(oClass, detachedState != null);
138 log.debug("Reading entity %s with fields %s", oClass.getName(), fields);
139 for (Property field : fields) {
140 if (field.getName().equals("jdoDetachedState"))
141 continue;
142
143 Object value = in.readObject();
144
145 if (!(field instanceof MethodProperty && field.isAnnotationPresent(ExternalizedProperty.class))) {
146
147 // (Un)Initialized collections/maps.
148 if (value instanceof AbstractExternalizablePersistentCollection)
149 value = newCollection((AbstractExternalizablePersistentCollection)value, field);
150 else
151 value = converters.convert(value, field.getType());
152
153 field.setProperty(o, value, false);
154 }
155 }
156 }
157 }
158
159 protected Object newCollection(AbstractExternalizablePersistentCollection value, Property field) {
160 final Type target = field.getType();
161 final boolean initialized = value.isInitialized();
162 // final boolean dirty = value.isDirty();
163 final Object[] content = value.getContent();
164 final boolean sorted = (
165 SortedSet.class.isAssignableFrom(ClassUtil.classOfType(target)) ||
166 SortedMap.class.isAssignableFrom(ClassUtil.classOfType(target))
167 );
168
169 Object coll = null;
170 if (value instanceof ExternalizablePersistentSet) {
171 if (initialized) {
172 if (content != null)
173 coll = ((ExternalizablePersistentSet)value).getContentAsSet(target);
174 }
175 else
176 coll = (sorted ? new TreeSet<Object>() : new HashSet<Object>());
177 }
178 else if (value instanceof ExternalizablePersistentList) {
179 if (initialized) {
180 if (content != null)
181 coll = ((ExternalizablePersistentList)value).getContentAsList(target);
182 }
183 else
184 coll = new ArrayList<Object>();
185 }
186 else if (value instanceof ExternalizablePersistentMap) {
187 if (initialized) {
188 if (content != null)
189 coll = ((ExternalizablePersistentMap)value).getContentAsMap(target);
190 }
191 else
192 coll = (sorted ? new TreeMap<Object, Object>() : new HashMap<Object, Object>());
193 }
194 else {
195 throw new RuntimeException("Illegal externalizable persitent class: " + value);
196 }
197
198 return coll;
199 }
200
201 @Override
202 public void writeExternal(Object o, ObjectOutput out) throws IOException, IllegalAccessException {
203
204 ClassGetter classGetter = GraniteContext.getCurrentInstance().getGraniteConfig().getClassGetter();
205 Class<?> oClass = classGetter.getClass(o);
206
207 if (!isRegularEntity(o.getClass())) { // @Embeddable or others...
208 log.debug("Delegating non regular entity writing to DefaultExternalizer...");
209 super.writeExternal(o, out);
210 }
211 else {
212 Detachable pco = (Detachable)o;
213 preSerialize((PersistenceCapable)pco);
214 Object[] detachedState = getDetachedState(pco);
215
216 // Pseudo-proxy created for uninitialized entities (see below).
217 if (detachedState != null && detachedState[0] == NULL_ID) {
218 // Write initialized flag.
219 out.writeObject(Boolean.FALSE);
220 // Write detached state.
221 out.writeObject(null);
222 // Write id.
223 out.writeObject(null);
224 return;
225 }
226
227 // Write initialized flag.
228 out.writeObject(Boolean.TRUE);
229
230 if (detachedState != null) {
231 // Write detached state as a String, in the form of an hex representation
232 // of the serialized detached state.
233 org.granite.util.Entity entity = new org.granite.util.Entity(pco);
234 Object version = entity.getVersion();
235 if (version != null)
236 detachedState[1] = version;
237 byte[] binDetachedState = serializeDetachedState(detachedState);
238 char[] hexDetachedState = StringUtil.bytesToHexChars(binDetachedState);
239 out.writeObject(new String(hexDetachedState));
240 }
241 else
242 out.writeObject(null);
243
244 // Externalize entity fields.
245 List<Property> fields = findOrderedFields(oClass);
246 Map<String, Boolean> loadedState = getLoadedState(detachedState, oClass);
247 log.debug("Writing entity %s with fields %s", o.getClass().getName(), fields);
248 for (Property field : fields) {
249 if (field.getName().equals("jdoDetachedState"))
250 continue;
251
252 Object value = field.getProperty(o);
253 if (isValueIgnored(value)) {
254 out.writeObject(null);
255 continue;
256 }
257
258 // Uninitialized associations.
259 if (loadedState.containsKey(field.getName()) && !loadedState.get(field.getName())) {
260 Class<?> fieldClass = ClassUtil.classOfType(field.getType());
261
262 // Create a "pseudo-proxy" for uninitialized entities: detached state is set to "0" (uninitialized flag).
263 if (Detachable.class.isAssignableFrom(fieldClass)) {
264 try {
265 value = fieldClass.newInstance();
266 } catch (Exception e) {
267 throw new RuntimeException("Could not create DataNucleus pseudo-proxy for: " + field, e);
268 }
269 setDetachedState((Detachable)value, new Object[] { NULL_ID, null, null, null });
270 }
271 // Create pseudo-proxy for collections (set or list).
272 else if (Collection.class.isAssignableFrom(fieldClass)) {
273 if (Set.class.isAssignableFrom(fieldClass))
274 value = new ExternalizablePersistentSet((Set<?>)null, false, false);
275 else
276 value = new ExternalizablePersistentList((List<?>)null, false, false);
277 }
278 // Create pseudo-proxy for maps.
279 else if (Map.class.isAssignableFrom(fieldClass)) {
280 value = new ExternalizablePersistentMap((Map<?, ?>)null, false, false);
281 }
282 }
283
284 // Initialized collections.
285 else if (value instanceof Set<?>) {
286 value = new ExternalizablePersistentSet(((Set<?>)value).toArray(), true, false);
287 }
288 else if (value instanceof List<?>) {
289 value = new ExternalizablePersistentList(((List<?>)value).toArray(), true, false);
290 }
291 else if (value instanceof Map<?, ?>) {
292 value = new ExternalizablePersistentMap((Map<?, ?>)null, true, false);
293 ((ExternalizablePersistentMap)value).setContentFromMap((Map<?, ?>)value);
294 }
295 out.writeObject(value);
296 }
297 }
298 }
299
300 @Override
301 public int accept(Class<?> clazz) {
302 return (
303 clazz.isAnnotationPresent(Entity.class) ||
304 clazz.isAnnotationPresent(MappedSuperclass.class) ||
305 clazz.isAnnotationPresent(Embeddable.class) ||
306 clazz.isAnnotationPresent(javax.jdo.annotations.PersistenceCapable.class)
307 ) ? 1 : -1;
308 }
309
310 protected boolean isRegularEntity(Class<?> clazz) {
311 return ((PersistenceCapable.class.isAssignableFrom(clazz) && Detachable.class.isAssignableFrom(clazz))
312 || clazz.isAnnotationPresent(Entity.class) || clazz.isAnnotationPresent(MappedSuperclass.class))
313 && !(clazz.isAnnotationPresent(Embeddable.class));
314 }
315
316 @Override
317 public List<Property> findOrderedFields(final Class<?> clazz, boolean returnSettersWhenAvailable) {
318 List<Property> orderedFields = super.findOrderedFields(clazz, returnSettersWhenAvailable);
319 if (clazz.isAnnotationPresent(Embeddable.class)) {
320 Iterator<Property> ifield = orderedFields.iterator();
321 while (ifield.hasNext()) {
322 Property field = ifield.next();
323 if (field.getName().equals("jdoDetachedState"))
324 ifield.remove();
325 }
326 }
327 return orderedFields;
328 }
329
330
331 private static void preSerialize(PersistenceCapable o) {
332 try {
333 Class<?> baseClass = o.getClass();
334 while (baseClass.getSuperclass() != Object.class &&
335 baseClass.getSuperclass() != null &&
336 PersistenceCapable.class.isAssignableFrom(baseClass.getSuperclass())) {
337 baseClass = baseClass.getSuperclass();
338 }
339 Field f = baseClass.getDeclaredField("jdoStateManager");
340 f.setAccessible(true);
341 StateManager sm = (StateManager)f.get(o);
342 if (sm != null) {
343 setDetachedState((Detachable)o, null);
344 sm.preSerialize(o);
345 }
346 }
347 catch (Exception e) {
348 throw new RuntimeException("Cannot access jdoDetachedState for detached object", e);
349 }
350 }
351
352 private static Object[] getDetachedState(javax.jdo.spi.Detachable o) {
353 try {
354 Class<?> baseClass = o.getClass();
355 while (baseClass.getSuperclass() != Object.class && baseClass.getSuperclass() != null && PersistenceCapable.class.isAssignableFrom(baseClass.getSuperclass()))
356 baseClass = baseClass.getSuperclass();
357 Field f = baseClass.getDeclaredField("jdoDetachedState");
358 f.setAccessible(true);
359 return (Object[])f.get(o);
360 }
361 catch (Exception e) {
362 throw new RuntimeException("Cannot access jdoDetachedState for detached object", e);
363 }
364 }
365
366 private static void setDetachedState(javax.jdo.spi.Detachable o, Object[] detachedState) {
367 try {
368 Class<?> baseClass = o.getClass();
369 while (baseClass.getSuperclass() != Object.class && baseClass.getSuperclass() != null && PersistenceCapable.class.isAssignableFrom(baseClass.getSuperclass()))
370 baseClass = baseClass.getSuperclass();
371 Field f = baseClass.getDeclaredField("jdoDetachedState");
372 f.setAccessible(true);
373 f.set(o, detachedState);
374 }
375 catch (Exception e) {
376 throw new RuntimeException("Cannot access jdoDetachedState for detached object", e);
377 }
378 }
379
380
381 static Map<String, Boolean> getLoadedState(Detachable pc, Class<?> clazz) {
382 return getLoadedState(getDetachedState(pc), clazz);
383 }
384
385 static Map<String, Boolean> getLoadedState(Object[] detachedState, Class<?> clazz) {
386 try {
387 BitSet loaded = detachedState != null ? (BitSet)detachedState[2] : null;
388
389 List<String> fieldNames = new ArrayList<String>();
390 for (Class<?> c = clazz; c != null && PersistenceCapable.class.isAssignableFrom(c); c = c.getSuperclass()) {
391 Field pcFieldNames = c.getDeclaredField("jdoFieldNames");
392 pcFieldNames.setAccessible(true);
393 fieldNames.addAll(0, Arrays.asList((String[])pcFieldNames.get(null)));
394 }
395
396 Map<String, Boolean> loadedState = new HashMap<String, Boolean>();
397 for (int i = 0; i < fieldNames.size(); i++)
398 loadedState.put(fieldNames.get(i), (loaded != null && loaded.size() > i ? loaded.get(i) : true));
399 return loadedState;
400 }
401 catch (Exception e) {
402 throw new RuntimeException("Could not get loaded state for: " + detachedState);
403 }
404 }
405
406 protected byte[] serializeDetachedState(Object[] detachedState) {
407 try {
408 // Force version
409 ByteArrayOutputStream baos = new ByteArrayOutputStream(256);
410 ObjectOutputStream oos = new ObjectOutputStream(baos);
411 oos.writeObject(detachedState);
412 return baos.toByteArray();
413 } catch (Exception e) {
414 throw new RuntimeException("Could not serialize detached state for: " + detachedState);
415 }
416 }
417
418 protected void deserializeDetachedState(Detachable pc, byte[] data) {
419 try {
420 ByteArrayInputStream baos = new ByteArrayInputStream(data);
421 ObjectInputStream oos = new ObjectInputStream(baos);
422 Object[] state = (Object[])oos.readObject();
423 setDetachedState(pc, state);
424 } catch (Exception e) {
425 throw new RuntimeException("Could not deserialize detached state for: " + data);
426 }
427 }
428 }