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.eclipselink;
022
023 import java.io.IOException;
024 import java.io.ObjectInput;
025 import java.io.ObjectOutput;
026 import java.lang.reflect.Field;
027 import java.lang.reflect.InvocationTargetException;
028 import java.lang.reflect.ParameterizedType;
029 import java.lang.reflect.Type;
030 import java.util.ArrayList;
031 import java.util.HashMap;
032 import java.util.List;
033 import java.util.Map;
034 import java.util.Set;
035
036 import javax.persistence.Embeddable;
037 import javax.persistence.Entity;
038 import javax.persistence.IdClass;
039 import javax.persistence.MappedSuperclass;
040
041 import org.eclipse.persistence.indirection.IndirectContainer;
042 import org.eclipse.persistence.indirection.IndirectList;
043 import org.eclipse.persistence.indirection.IndirectMap;
044 import org.eclipse.persistence.indirection.IndirectSet;
045 import org.eclipse.persistence.indirection.ValueHolderInterface;
046 import org.granite.collections.BasicMap;
047 import org.granite.config.GraniteConfig;
048 import org.granite.context.GraniteContext;
049 import org.granite.logging.Logger;
050 import org.granite.messaging.amf.io.convert.Converters;
051 import org.granite.messaging.amf.io.util.ClassGetter;
052 import org.granite.messaging.amf.io.util.MethodProperty;
053 import org.granite.messaging.amf.io.util.Property;
054 import org.granite.messaging.amf.io.util.externalizer.DefaultExternalizer;
055 import org.granite.messaging.annotations.Include;
056 import org.granite.messaging.persistence.AbstractExternalizablePersistentCollection;
057 import org.granite.messaging.persistence.ExternalizablePersistentList;
058 import org.granite.messaging.persistence.ExternalizablePersistentMap;
059 import org.granite.messaging.persistence.ExternalizablePersistentSet;
060 import org.granite.util.TypeUtil;
061
062 /**
063 * @author William DRAI
064 */
065 public class EclipseLinkExternalizer extends DefaultExternalizer {
066
067 private static final Logger log = Logger.getLogger(EclipseLinkExternalizer.class);
068
069 @Override
070 public Object newInstance(String type, ObjectInput in)
071 throws IOException, ClassNotFoundException, InstantiationException, InvocationTargetException, IllegalAccessException {
072
073 // If type is not an entity (@Embeddable for example), we don't read initialized/detachedState
074 // and we fall back to DefaultExternalizer behavior.
075 Class<?> clazz = TypeUtil.forName(type);
076 if (!isRegularEntity(clazz))
077 return super.newInstance(type, in);
078
079 // Read initialized flag.
080 boolean initialized = ((Boolean)in.readObject()).booleanValue();
081
082 // Read detached state.
083 @SuppressWarnings("unused")
084 String detachedState = (String)in.readObject();
085
086 // New or initialized entity.
087 if (initialized)
088 return super.newInstance(type, in);
089
090 // Pseudo-proxy (uninitialized entity).
091 Object id = in.readObject();
092 if (id != null && (!clazz.isAnnotationPresent(IdClass.class) || !clazz.getAnnotation(IdClass.class).value().equals(id.getClass())))
093 throw new RuntimeException("Id for EclipseLink pseudo-proxy should be null or IdClass (" + type + ")");
094
095 return new EclipseLinkValueHolder();
096 }
097
098 @Override
099 public void readExternal(Object o, ObjectInput in) throws IOException, ClassNotFoundException, IllegalAccessException {
100 // Ignore EclipseLink proxies
101 if (o instanceof EclipseLinkValueHolder)
102 return;
103
104 // @Embeddable or others...
105 if (!isRegularEntity(o.getClass()) && !isEmbeddable(o.getClass())) {
106 log.debug("Delegating non regular entity reading to DefaultExternalizer...");
107 super.readExternal(o, in);
108 }
109 // Regural @Entity or @MappedSuperclass
110 else {
111 GraniteConfig config = GraniteContext.getCurrentInstance().getGraniteConfig();
112
113 Converters converters = config.getConverters();
114 ClassGetter classGetter = config.getClassGetter();
115 Class<?> oClass = classGetter.getClass(o);
116 ParameterizedType[] declaringTypes = TypeUtil.getDeclaringTypes(oClass);
117
118 List<Property> fields = findOrderedFields(oClass);
119 log.debug("Reading entity %s with fields %s", oClass.getName(), fields);
120 Map<String, Property> topLinkFields = new HashMap<String, Property>();
121 for (Property field : fields) {
122 if (field.getType() instanceof Class<?> && ValueHolderInterface.class.isAssignableFrom((Class<?>)field.getType())) {
123 topLinkFields.put(field.getName(), field);
124 }
125 else {
126 Object value = in.readObject();
127
128 if (value instanceof ValueHolderInterface) {
129 topLinkFields.get("_persistence_" + field.getName() + "_vh").setProperty(o, value, false);
130 }
131 else if (!(field instanceof MethodProperty && field.isAnnotationPresent(Include.class, true))) {
132 if (value instanceof AbstractExternalizablePersistentCollection)
133 value = newIndirectCollection((AbstractExternalizablePersistentCollection)value, field.getType());
134 else {
135 Type targetType = TypeUtil.resolveTypeVariable(field.getType(), field.getDeclaringClass(), declaringTypes);
136 value = converters.convert(value, targetType);
137 }
138
139 field.setProperty(o, value, false);
140 }
141 }
142 }
143 }
144 }
145
146 @Override
147 protected boolean isPropertyIgnored(Field field) {
148 return field.getName().equals("_persistence_fetchGroup");
149 }
150
151
152 protected IndirectContainer newIndirectCollection(AbstractExternalizablePersistentCollection value, Type target) {
153 final boolean initialized = value.isInitialized();
154 final Object[] content = value.getContent();
155
156 IndirectContainer coll = null;
157 if (value instanceof ExternalizablePersistentSet) {
158 if (initialized) {
159 if (content != null) {
160 Set<?> set = ((ExternalizablePersistentSet)value).getContentAsSet(target);
161 coll = new IndirectSet(set);
162 }
163 }
164 else
165 coll = new IndirectSet();
166 }
167 else if (value instanceof ExternalizablePersistentList) {
168 if (initialized) {
169 if (content != null) {
170 List<?> list = ((ExternalizablePersistentList)value).getContentAsList(target);
171 coll = new IndirectList(list);
172 }
173 }
174 else
175 coll = new IndirectList();
176 }
177 else if (value instanceof ExternalizablePersistentMap) {
178 if (initialized) {
179 if (content != null) {
180 Map<?, ?> map = ((ExternalizablePersistentMap)value).getContentAsMap(target);
181 coll = new IndirectMap(map);
182 }
183 }
184 else
185 coll = new IndirectMap();
186 }
187 else {
188 throw new RuntimeException("Illegal externalizable persitent class: " + value);
189 }
190
191 return coll;
192 }
193
194
195 @Override
196 public void writeExternal(Object o, ObjectOutput out) throws IOException, IllegalAccessException {
197
198 ClassGetter classGetter = GraniteContext.getCurrentInstance().getGraniteConfig().getClassGetter();
199 Class<?> oClass = classGetter.getClass(o);
200
201 if (o instanceof EclipseLinkProxy) {
202 EclipseLinkProxy proxy = (EclipseLinkProxy)o;
203
204 // Only write initialized flag, null detachedState & null id if proxy is uninitialized.
205 String description = proxy.getProxiedClass().getName();
206 log.debug("Writing uninitialized EclipseLink ValueHolder %s", description);
207
208 // Write initialized flag.
209 out.writeObject(Boolean.FALSE);
210 // Write detachedState.
211 out.writeObject(null);
212 // Write id.
213 out.writeObject(null);
214
215 return;
216 }
217
218 if (!isRegularEntity(o.getClass()) && !isEmbeddable(o.getClass())) { // @Embeddable or others...
219 log.debug("Delegating non regular entity writing to DefaultExternalizer...");
220 super.writeExternal(o, out);
221 }
222 else {
223 if (isRegularEntity(o.getClass())) {
224 // Write initialized flag.
225 out.writeObject(Boolean.TRUE);
226 // Write detachedState.
227 out.writeObject(null);
228 }
229
230 // Externalize entity fields.
231 List<Property> fields = findOrderedFields(oClass);
232 List<String> lazyFieldNames = new ArrayList<String>();
233
234 log.debug("Writing entity %s with fields %s", o.getClass().getName(), fields);
235 for (Property field : fields) {
236 if (!(field.getType() instanceof Class<?> && ValueHolderInterface.class.isAssignableFrom((Class<?>)field.getType()))) {
237 if (lazyFieldNames.contains(field.getName())) {
238 EclipseLinkProxy proxy = new EclipseLinkProxy((Class<?>)field.getType());
239 out.writeObject(proxy);
240 }
241 else {
242 Object value = field.getProperty(o);
243
244 // Persistent collections & maps.
245 if (value instanceof IndirectContainer)
246 value = newExternalizableCollection((IndirectContainer)value);
247 // Transient maps.
248 else if (value instanceof Map<?, ?>)
249 value = BasicMap.newInstance((Map<?, ?>)value);
250
251 out.writeObject(value);
252 }
253 }
254 else {
255 ValueHolderInterface vh = (ValueHolderInterface)field.getProperty(o);
256 if (vh != null && !vh.isInstantiated())
257 lazyFieldNames.add(field.getName().substring("_persistence_".length(), field.getName().length()-3));
258 }
259 }
260 }
261 }
262
263 protected AbstractExternalizablePersistentCollection newExternalizableCollection(IndirectContainer value) {
264 AbstractExternalizablePersistentCollection coll = null;
265 boolean initialized = value.isInstantiated();
266
267 if (value instanceof IndirectSet) {
268 coll = new ExternalizablePersistentSet(initialized ? ((IndirectSet)value).toArray() : null, initialized, false);
269 }
270 else if (value instanceof IndirectList) {
271 coll = new ExternalizablePersistentList(initialized ? ((IndirectList)value).toArray() : null, initialized, false);
272 }
273 else if (value instanceof IndirectMap) {
274 Object[] content = null;
275
276 if (initialized) {
277 content = new Object[((IndirectMap)value).size()];
278 int index = 0;
279 @SuppressWarnings("unchecked")
280 Set<Map.Entry<?, ?>> entries = ((IndirectMap)value).entrySet();
281 for (Map.Entry<?, ?> entry : entries)
282 content[index++] = new Object[]{entry.getKey(), entry.getValue()};
283 }
284
285 coll = new ExternalizablePersistentMap(content, initialized, false);
286 }
287 else {
288 throw new UnsupportedOperationException("Unsupported EclipseLink collection type: " + value);
289 }
290
291 return coll;
292 }
293
294
295 @Override
296 public int accept(Class<?> clazz) {
297 return (
298 clazz.isAnnotationPresent(Entity.class) ||
299 clazz.isAnnotationPresent(MappedSuperclass.class) ||
300 clazz.isAnnotationPresent(Embeddable.class)
301 ) ? 1 : -1;
302 }
303
304 protected boolean isRegularEntity(Class<?> clazz) {
305 return clazz.isAnnotationPresent(Entity.class) || clazz.isAnnotationPresent(MappedSuperclass.class);
306 }
307
308 protected boolean isEmbeddable(Class<?> clazz) {
309 return clazz.isAnnotationPresent(Embeddable.class);
310 }
311 }