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