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()) && !isEmbeddable(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()) && !isEmbeddable(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 if (isRegularEntity(o.getClass())) {
217 // Pseudo-proxy created for uninitialized entities (see below).
218 if (detachedState != null && detachedState[0] == NULL_ID) {
219 // Write initialized flag.
220 out.writeObject(Boolean.FALSE);
221 // Write detached state.
222 out.writeObject(null);
223 // Write id.
224 out.writeObject(null);
225 return;
226 }
227
228 // Write initialized flag.
229 out.writeObject(Boolean.TRUE);
230
231 if (detachedState != null) {
232 // Write detached state as a String, in the form of an hex representation
233 // of the serialized detached state.
234 org.granite.util.Entity entity = new org.granite.util.Entity(pco);
235 Object version = entity.getVersion();
236 if (version != null)
237 detachedState[1] = version;
238 byte[] binDetachedState = serializeDetachedState(detachedState);
239 char[] hexDetachedState = StringUtil.bytesToHexChars(binDetachedState);
240 out.writeObject(new String(hexDetachedState));
241 }
242 else
243 out.writeObject(null);
244 }
245
246 // Externalize entity fields.
247 List<Property> fields = findOrderedFields(oClass);
248 Map<String, Boolean> loadedState = getLoadedState(detachedState, oClass);
249 log.debug("Writing entity %s with fields %s", o.getClass().getName(), fields);
250 for (Property field : fields) {
251 if (field.getName().equals("jdoDetachedState"))
252 continue;
253
254 Object value = field.getProperty(o);
255 if (isValueIgnored(value)) {
256 out.writeObject(null);
257 continue;
258 }
259
260 // Uninitialized associations.
261 if (loadedState.containsKey(field.getName()) && !loadedState.get(field.getName())) {
262 Class<?> fieldClass = ClassUtil.classOfType(field.getType());
263
264 // Create a "pseudo-proxy" for uninitialized entities: detached state is set to "0" (uninitialized flag).
265 if (Detachable.class.isAssignableFrom(fieldClass)) {
266 try {
267 value = fieldClass.newInstance();
268 } catch (Exception e) {
269 throw new RuntimeException("Could not create DataNucleus pseudo-proxy for: " + field, e);
270 }
271 setDetachedState((Detachable)value, new Object[] { NULL_ID, null, null, null });
272 }
273 // Create pseudo-proxy for collections (set or list).
274 else if (Collection.class.isAssignableFrom(fieldClass)) {
275 if (Set.class.isAssignableFrom(fieldClass))
276 value = new ExternalizablePersistentSet((Set<?>)null, false, false);
277 else
278 value = new ExternalizablePersistentList((List<?>)null, false, false);
279 }
280 // Create pseudo-proxy for maps.
281 else if (Map.class.isAssignableFrom(fieldClass)) {
282 value = new ExternalizablePersistentMap((Map<?, ?>)null, false, false);
283 }
284 }
285
286 // Initialized collections.
287 else if (value instanceof Set<?>) {
288 value = new ExternalizablePersistentSet(((Set<?>)value).toArray(), true, false);
289 }
290 else if (value instanceof List<?>) {
291 value = new ExternalizablePersistentList(((List<?>)value).toArray(), true, false);
292 }
293 else if (value instanceof Map<?, ?>) {
294 value = new ExternalizablePersistentMap((Map<?, ?>)null, true, false);
295 ((ExternalizablePersistentMap)value).setContentFromMap((Map<?, ?>)value);
296 }
297 out.writeObject(value);
298 }
299 }
300 }
301
302 @Override
303 public int accept(Class<?> clazz) {
304 return (
305 clazz.isAnnotationPresent(Entity.class) ||
306 clazz.isAnnotationPresent(MappedSuperclass.class) ||
307 clazz.isAnnotationPresent(Embeddable.class) ||
308 clazz.isAnnotationPresent(javax.jdo.annotations.PersistenceCapable.class)
309 ) ? 1 : -1;
310 }
311
312 protected boolean isRegularEntity(Class<?> clazz) {
313 return ((PersistenceCapable.class.isAssignableFrom(clazz) && Detachable.class.isAssignableFrom(clazz))
314 || clazz.isAnnotationPresent(Entity.class) || clazz.isAnnotationPresent(MappedSuperclass.class))
315 && !(clazz.isAnnotationPresent(Embeddable.class));
316 }
317
318 protected boolean isEmbeddable(Class<?> clazz) {
319 return ((PersistenceCapable.class.isAssignableFrom(clazz) && Detachable.class.isAssignableFrom(clazz))
320 || clazz.isAnnotationPresent(Embeddable.class))
321 && !(clazz.isAnnotationPresent(Entity.class) || clazz.isAnnotationPresent(MappedSuperclass.class));
322 }
323
324 @Override
325 public List<Property> findOrderedFields(final Class<?> clazz, boolean returnSettersWhenAvailable) {
326 List<Property> orderedFields = super.findOrderedFields(clazz, returnSettersWhenAvailable);
327 if (clazz.isAnnotationPresent(Embeddable.class)) {
328 Iterator<Property> ifield = orderedFields.iterator();
329 while (ifield.hasNext()) {
330 Property field = ifield.next();
331 if (field.getName().equals("jdoDetachedState"))
332 ifield.remove();
333 }
334 }
335 return orderedFields;
336 }
337
338
339 private static void preSerialize(PersistenceCapable o) {
340 try {
341 Class<?> baseClass = o.getClass();
342 while (baseClass.getSuperclass() != Object.class &&
343 baseClass.getSuperclass() != null &&
344 PersistenceCapable.class.isAssignableFrom(baseClass.getSuperclass())) {
345 baseClass = baseClass.getSuperclass();
346 }
347 Field f = baseClass.getDeclaredField("jdoStateManager");
348 f.setAccessible(true);
349 StateManager sm = (StateManager)f.get(o);
350 if (sm != null) {
351 setDetachedState((Detachable)o, null);
352 sm.preSerialize(o);
353 }
354 }
355 catch (Exception e) {
356 throw new RuntimeException("Cannot access jdoDetachedState for detached object", e);
357 }
358 }
359
360 private static Object[] getDetachedState(javax.jdo.spi.Detachable o) {
361 try {
362 Class<?> baseClass = o.getClass();
363 while (baseClass.getSuperclass() != Object.class && baseClass.getSuperclass() != null && PersistenceCapable.class.isAssignableFrom(baseClass.getSuperclass()))
364 baseClass = baseClass.getSuperclass();
365 Field f = baseClass.getDeclaredField("jdoDetachedState");
366 f.setAccessible(true);
367 return (Object[])f.get(o);
368 }
369 catch (Exception e) {
370 throw new RuntimeException("Cannot access jdoDetachedState for detached object", e);
371 }
372 }
373
374 private static void setDetachedState(javax.jdo.spi.Detachable o, Object[] detachedState) {
375 try {
376 Class<?> baseClass = o.getClass();
377 while (baseClass.getSuperclass() != Object.class && baseClass.getSuperclass() != null && PersistenceCapable.class.isAssignableFrom(baseClass.getSuperclass()))
378 baseClass = baseClass.getSuperclass();
379 Field f = baseClass.getDeclaredField("jdoDetachedState");
380 f.setAccessible(true);
381 f.set(o, detachedState);
382 }
383 catch (Exception e) {
384 throw new RuntimeException("Cannot access jdoDetachedState for detached object", e);
385 }
386 }
387
388
389 static Map<String, Boolean> getLoadedState(Detachable pc, Class<?> clazz) {
390 return getLoadedState(getDetachedState(pc), clazz);
391 }
392
393 static Map<String, Boolean> getLoadedState(Object[] detachedState, Class<?> clazz) {
394 try {
395 BitSet loaded = detachedState != null ? (BitSet)detachedState[2] : null;
396
397 List<String> fieldNames = new ArrayList<String>();
398 for (Class<?> c = clazz; c != null && PersistenceCapable.class.isAssignableFrom(c); c = c.getSuperclass()) {
399 Field pcFieldNames = c.getDeclaredField("jdoFieldNames");
400 pcFieldNames.setAccessible(true);
401 fieldNames.addAll(0, Arrays.asList((String[])pcFieldNames.get(null)));
402 }
403
404 Map<String, Boolean> loadedState = new HashMap<String, Boolean>();
405 for (int i = 0; i < fieldNames.size(); i++)
406 loadedState.put(fieldNames.get(i), (loaded != null && loaded.size() > i ? loaded.get(i) : true));
407 return loadedState;
408 }
409 catch (Exception e) {
410 throw new RuntimeException("Could not get loaded state for: " + detachedState);
411 }
412 }
413
414 protected byte[] serializeDetachedState(Object[] detachedState) {
415 try {
416 // Force version
417 ByteArrayOutputStream baos = new ByteArrayOutputStream(256);
418 ObjectOutputStream oos = new ObjectOutputStream(baos);
419 oos.writeObject(detachedState);
420 return baos.toByteArray();
421 } catch (Exception e) {
422 throw new RuntimeException("Could not serialize detached state for: " + detachedState);
423 }
424 }
425
426 protected void deserializeDetachedState(Detachable pc, byte[] data) {
427 try {
428 ByteArrayInputStream baos = new ByteArrayInputStream(data);
429 ObjectInputStream oos = new ObjectInputStream(baos);
430 Object[] state = (Object[])oos.readObject();
431 setDetachedState(pc, state);
432 } catch (Exception e) {
433 throw new RuntimeException("Could not deserialize detached state for: " + data);
434 }
435 }
436 }