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