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.toplink;
022
023 import java.io.IOException;
024 import java.io.ObjectInput;
025 import java.io.ObjectOutput;
026 import java.lang.reflect.InvocationTargetException;
027 import java.lang.reflect.ParameterizedType;
028 import java.lang.reflect.Type;
029 import java.util.ArrayList;
030 import java.util.HashMap;
031 import java.util.List;
032 import java.util.Map;
033 import java.util.Set;
034
035 import javax.persistence.Embeddable;
036 import javax.persistence.Entity;
037 import javax.persistence.IdClass;
038 import javax.persistence.MappedSuperclass;
039
040 import oracle.toplink.essentials.indirection.IndirectContainer;
041 import oracle.toplink.essentials.indirection.IndirectList;
042 import oracle.toplink.essentials.indirection.IndirectMap;
043 import oracle.toplink.essentials.indirection.IndirectSet;
044 import oracle.toplink.essentials.indirection.ValueHolderInterface;
045
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.amf.io.util.externalizer.annotation.ExternalizedProperty;
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 TopLinkExternalizer extends DefaultExternalizer {
066
067 private static final Logger log = Logger.getLogger(TopLinkExternalizer.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 TopLink pseudo-proxy should be null or IdClass (" + type + ")");
094
095 return new TopLinkValueHolder();
096 }
097
098 @Override
099 public void readExternal(Object o, ObjectInput in) throws IOException, ClassNotFoundException, IllegalAccessException {
100 // Ignore TopLink proxies
101 if (o instanceof TopLinkValueHolder)
102 return;
103
104 // @Embeddable or others...
105 if (!isRegularEntity(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("_toplink_" + field.getName() + "_vh").setProperty(o, value, false);
130 }
131 else if (!(field instanceof MethodProperty && field.isAnnotationPresent(ExternalizedProperty.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 protected IndirectContainer newIndirectCollection(AbstractExternalizablePersistentCollection value, Type target) {
147 final boolean initialized = value.isInitialized();
148 final Object[] content = value.getContent();
149
150 IndirectContainer coll = null;
151 if (value instanceof ExternalizablePersistentSet) {
152 if (initialized) {
153 if (content != null) {
154 Set<?> set = ((ExternalizablePersistentSet)value).getContentAsSet(target);
155 coll = new IndirectSet(set);
156 }
157 }
158 else
159 coll = new IndirectSet();
160 }
161 else if (value instanceof ExternalizablePersistentList) {
162 if (initialized) {
163 if (content != null) {
164 List<?> list = ((ExternalizablePersistentList)value).getContentAsList(target);
165 coll = new IndirectList(list);
166 }
167 }
168 else
169 coll = new IndirectList();
170 }
171 else if (value instanceof ExternalizablePersistentMap) {
172 if (initialized) {
173 if (content != null) {
174 Map<?, ?> map = ((ExternalizablePersistentMap)value).getContentAsMap(target);
175 coll = new IndirectMap(map);
176 }
177 }
178 else
179 coll = new IndirectMap();
180 }
181 else {
182 throw new RuntimeException("Illegal externalizable persitent class: " + value);
183 }
184
185 return coll;
186 }
187
188
189 @Override
190 public void writeExternal(Object o, ObjectOutput out) throws IOException, IllegalAccessException {
191
192 ClassGetter classGetter = GraniteContext.getCurrentInstance().getGraniteConfig().getClassGetter();
193 Class<?> oClass = classGetter.getClass(o);
194
195 if (o instanceof TopLinkProxy) {
196 TopLinkProxy proxy = (TopLinkProxy)o;
197
198 // Only write initialized flag, null detachedState & null id if proxy is uninitialized.
199 log.debug("Writing uninitialized TopLink ValueHolder %s", proxy.getProxiedClass().getName());
200
201 // Write initialized flag.
202 out.writeObject(Boolean.FALSE);
203 // Write detachedState.
204 out.writeObject(null);
205 // Write id.
206 out.writeObject(null);
207
208 return;
209 }
210
211 if (!isRegularEntity(o.getClass())) { // @Embeddable or others...
212 log.debug("Delegating non regular entity writing to DefaultExternalizer...");
213 super.writeExternal(o, out);
214 }
215 else {
216 // Write initialized flag.
217 out.writeObject(Boolean.TRUE);
218 // Write detachedState.
219 out.writeObject(null);
220
221 // Externalize entity fields.
222 List<Property> fields = findOrderedFields(oClass);
223 List<String> lazyFieldNames = new ArrayList<String>();
224
225 log.debug("Writing entity %s with fields %s", o.getClass().getName(), fields);
226 for (Property field : fields) {
227 if (!(field.getType() instanceof Class<?> && ValueHolderInterface.class.isAssignableFrom((Class<?>)field.getType()))) {
228 if (lazyFieldNames.contains(field.getName())) {
229 TopLinkProxy proxy = new TopLinkProxy((Class<?>)field.getType());
230 out.writeObject(proxy);
231 }
232 else {
233 Object value = field.getProperty(o);
234
235 // Persistent collections & maps.
236 if (value instanceof IndirectContainer)
237 value = newExternalizableCollection((IndirectContainer)value);
238 // Transient maps.
239 else if (value instanceof Map<?, ?>)
240 value = BasicMap.newInstance((Map<?, ?>)value);
241
242 out.writeObject(value);
243 }
244 }
245 else {
246 ValueHolderInterface vh = (ValueHolderInterface)field.getProperty(o);
247 if (!vh.isInstantiated())
248 lazyFieldNames.add(field.getName().substring("_toplink_".length(), field.getName().length()-3));
249 }
250 }
251 }
252 }
253
254 protected AbstractExternalizablePersistentCollection newExternalizableCollection(IndirectContainer value) {
255 AbstractExternalizablePersistentCollection coll = null;
256 boolean initialized = value.isInstantiated();
257
258 if (value instanceof IndirectSet) {
259 coll = new ExternalizablePersistentSet(initialized ? ((IndirectSet)value).toArray() : null, initialized, false);
260 }
261 else if (value instanceof IndirectList) {
262 coll = new ExternalizablePersistentList(initialized ? ((IndirectList)value).toArray() : null, initialized, false);
263 }
264 else if (value instanceof IndirectMap) {
265 Object[] content = null;
266
267 if (initialized) {
268 content = new Object[((IndirectMap)value).size()];
269 int index = 0;
270 @SuppressWarnings("unchecked")
271 Set<Map.Entry<?, ?>> entries = ((IndirectMap)value).entrySet();
272 for (Map.Entry<?, ?> entry : entries)
273 content[index++] = new Object[]{entry.getKey(), entry.getValue()};
274 }
275
276 coll = new ExternalizablePersistentMap(content, initialized, false);
277 }
278 else {
279 throw new UnsupportedOperationException("Unsupported TopLink collection type: " + value);
280 }
281
282 return coll;
283 }
284
285
286 @Override
287 public int accept(Class<?> clazz) {
288 return (
289 clazz.isAnnotationPresent(Entity.class) ||
290 clazz.isAnnotationPresent(MappedSuperclass.class) ||
291 clazz.isAnnotationPresent(Embeddable.class)
292 ) ? 1 : -1;
293 }
294
295 protected boolean isRegularEntity(Class<?> clazz) {
296 return clazz.isAnnotationPresent(Entity.class) || clazz.isAnnotationPresent(MappedSuperclass.class);
297 }
298 }