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