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