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