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