001package co.codewizards.cloudstore.ls.core.provider; 002 003import static co.codewizards.cloudstore.core.util.ReflectionUtil.*; 004import static java.util.Objects.*; 005 006import java.io.IOException; 007import java.io.ObjectOutputStream; 008import java.io.OutputStream; 009import java.lang.annotation.Annotation; 010import java.lang.reflect.Array; 011import java.lang.reflect.Field; 012import java.lang.reflect.Modifier; 013import java.lang.reflect.Type; 014import java.util.ArrayList; 015import java.util.Collection; 016import java.util.HashMap; 017import java.util.IdentityHashMap; 018import java.util.LinkedList; 019import java.util.List; 020import java.util.Map; 021 022import javax.ws.rs.Produces; 023import javax.ws.rs.WebApplicationException; 024import javax.ws.rs.core.Context; 025import javax.ws.rs.core.MediaType; 026import javax.ws.rs.core.MultivaluedMap; 027import javax.ws.rs.core.SecurityContext; 028import javax.ws.rs.ext.MessageBodyWriter; 029import javax.ws.rs.ext.Provider; 030 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033 034import co.codewizards.cloudstore.core.Uid; 035import co.codewizards.cloudstore.core.io.NoCloseOutputStream; 036import co.codewizards.cloudstore.core.ls.NoObjectRef; 037import co.codewizards.cloudstore.ls.core.invoke.ForceNonTransientClassSet; 038import co.codewizards.cloudstore.ls.core.invoke.ForceNonTransientContainer; 039import co.codewizards.cloudstore.ls.core.invoke.ObjectGraphContainer; 040import co.codewizards.cloudstore.ls.core.invoke.ObjectManager; 041import co.codewizards.cloudstore.ls.core.invoke.ObjectRef; 042import co.codewizards.cloudstore.ls.core.invoke.ObjectRefConverter; 043import co.codewizards.cloudstore.ls.core.invoke.ObjectRefConverterFactory; 044 045/** 046 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de 047 */ 048@Provider 049@Produces(MediaTypeConst.APPLICATION_JAVA_NATIVE_WITH_OBJECT_REF) 050public class JavaNativeWithObjectRefMessageBodyWriter 051implements MessageBodyWriter<Object> 052{ 053 private final ObjectRefConverterFactory objectRefConverterFactory; 054 055 @Context 056 private SecurityContext securityContext; 057 058 public JavaNativeWithObjectRefMessageBodyWriter(final ObjectRefConverterFactory objectRefConverterFactory) { 059 this.objectRefConverterFactory = requireNonNull(objectRefConverterFactory, "objectRefConverterFactory"); 060 } 061 062 @Override 063 public long getSize(Object t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) 064 { 065 return -1; 066 } 067 068 @Override 069 public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { 070 // We return always true, because we declared our media-type already in the @Produces above and thus don't need to check it here. 071 // At least I hope we don't get consulted for media-types that were not declared in @Produces. 072 return true; 073 } 074 075 @Override 076 public void writeTo( 077 Object t, Class<?> type, Type genericType, 078 Annotation[] annotations, MediaType mediaType, 079 MultivaluedMap<String, Object> httpHeaders, 080 OutputStream entityStream 081 ) throws IOException, WebApplicationException 082 { 083 final ObjectRefConverter objectRefConverter = objectRefConverterFactory.createObjectRefConverter(securityContext); 084 final ObjectGraphContainer objectGraphContainer = new ObjectGraphContainer(t); 085 final NoObjectRefAnalyser noObjectRefAnalyser = new NoObjectRefAnalyser(objectGraphContainer); 086 try (ObjectOutputStream oout = new ReplacingObjectOutputStream(new NoCloseOutputStream(entityStream), objectRefConverter, noObjectRefAnalyser, objectGraphContainer);) { 087 oout.writeObject(objectGraphContainer); 088 oout.flush(); 089 } 090 } 091 092 private static class ReplacingObjectOutputStream extends ObjectOutputStream { 093 private final NoObjectRefAnalyser noObjectRefAnalyser; 094 private final ObjectRefConverter objectRefConverter; 095 private final ObjectGraphContainer objectGraphContainer; 096 097 public ReplacingObjectOutputStream(OutputStream out, final ObjectRefConverter objectRefConverter, final NoObjectRefAnalyser noObjectRefAnalyser, final ObjectGraphContainer objectGraphContainer) throws IOException { 098 super(out); 099 this.objectRefConverter = requireNonNull(objectRefConverter, "objectRefConverter"); 100 this.noObjectRefAnalyser = requireNonNull(noObjectRefAnalyser, "noObjectRefAnalyser"); 101 this.objectGraphContainer = requireNonNull(objectGraphContainer, "objectGraphContainer"); 102 enableReplaceObject(true); 103 } 104 105 @Override 106 protected Object replaceObject(final Object object) throws IOException { 107 if (object == null || object instanceof ObjectRef || object instanceof Uid) // we replace the objects by ObjectRefs, hence we must ignore the replaced objects here - they are unknown to our analyser. 108 return object; 109 110 if (noObjectRefAnalyser.isNoObjectRef(object)) 111 return object; 112 113 final Object result = objectRefConverter.convertToObjectRefIfNeeded(object); 114 if (result != object) 115 noObjectRefAnalyser.analyse(result); 116 117 if (ForceNonTransientClassSet.getInstance().isForceNonTransientClass(result.getClass())) { 118 final List<Field> transientFields = getTransientFields(result.getClass()); 119 if (!transientFields.isEmpty()) { 120 final ForceNonTransientContainer forceNonTransientContainer = createForceNonTransientContainer(result, transientFields); 121 objectGraphContainer.putForceNonTransientContainer(forceNonTransientContainer); 122 } 123 } 124 return result; 125 } 126 127 private ForceNonTransientContainer createForceNonTransientContainer(final Object object, final List<Field> transientFields) { 128 final Map<String, Object> transientFieldName2Value = new HashMap<>(); 129 for (final Field field : transientFields) { 130 field.setAccessible(true); 131 132 final Object fieldValue; 133 try { 134 fieldValue = field.get(object); 135 } catch (IllegalAccessException e) { 136 throw new RuntimeException(e); 137 } 138 139 final String fieldName = field.getDeclaringClass().getName() + '.' + field.getName(); 140 transientFieldName2Value.put(fieldName, fieldValue); 141 } 142 return new ForceNonTransientContainer(object, transientFieldName2Value); 143 } 144 145 private List<Field> getTransientFields(Class<?> clazz) { 146 final List<Field> allDeclaredFields = getAllDeclaredFields(clazz); 147 final List<Field> transientFields = new ArrayList<>(); 148 for (final Field field : allDeclaredFields) { 149 if ((Modifier.TRANSIENT & field.getModifiers()) != 0) 150 transientFields.add(field); 151 } 152 return transientFields; 153 } 154 } 155 156 private static NoObjectRef NO_OBJECT_REF_FALLBACK = new NoObjectRef() { 157 @Override 158 public Class<? extends Annotation> annotationType() { 159 return NoObjectRef.class; 160 } 161 162 @Override 163 public boolean value() { 164 return false; 165 } 166 167 @Override 168 public boolean inheritToObjectGraphChildren() { 169 return true; 170 } 171 }; 172 173 private static class NoObjectRefAnalyser { 174 private static final Logger logger = LoggerFactory.getLogger(JavaNativeWithObjectRefMessageBodyWriter.NoObjectRefAnalyser.class); 175 176 private final IdentityHashMap<Object, NoObjectRef> object2NoObjectRef = new IdentityHashMap<>(); 177 178 public NoObjectRefAnalyser(final Object root) { 179 analyse(root); 180 } 181 182 public void analyse(Object root) { 183 analyse(new LinkedList<Object>(), null, root, new IdentityHashMap<Object, Void>()); 184 } 185 186 private void analyse(final LinkedList<Object> parentStack, final Object parent, final Object object, final IdentityHashMap<Object, Void> processedObjects) { 187 if (object == null) 188 return; 189 190 if (parentStack.size() >= 4) 191 return; 192 193 if (parent != null) 194 parentStack.addLast(parent); 195 196 try { 197 if (processedObjects.containsKey(object)) 198 return; 199 200 logger.debug("analyse: object={}", object); 201 202 processedObjects.put(object, null); 203 204 final NoObjectRef noObjectRefVal = object2NoObjectRef.get(object); 205 if (noObjectRefVal == null) { 206 final NoObjectRef noObjectRef = object.getClass().getAnnotation(NoObjectRef.class); 207 if (noObjectRef == null) { 208 final NoObjectRef parentNoObjectRef = parent == null ? NO_OBJECT_REF_FALLBACK : object2NoObjectRef.get(parent); 209 object2NoObjectRef.put(object, parentNoObjectRef); 210 } 211 else 212 object2NoObjectRef.put(object, noObjectRef); 213 } 214 215 if (isTypeConsideredLeaf(object)) 216 return; 217 218 if (object.getClass().isArray()) { 219 // We do not treat children of a simple array, because they are no objects and ObjectOutputStream.replaceObject(...) should thus *not* be called for them. 220 if (Object.class.isAssignableFrom(object.getClass().getComponentType())) { 221 final int length = Array.getLength(object); 222 for (int i = 0; i < length; ++i) { 223 final Object child = Array.get(object, i); 224 analyse(parentStack, object, child, processedObjects); 225 } 226 } 227 } 228 else if (object instanceof Collection<?>) { 229 final Collection<?> c = (Collection<?>) object; 230 for (final Object child : c) 231 analyse(parentStack, object, child, processedObjects); 232 } 233 else if (object instanceof Map<?, ?>) { 234 final Map<?, ?> m = (Map<?, ?>) object; 235 for (Map.Entry<?, ?> me : m.entrySet()) { 236 analyse(parentStack, object, me.getKey(), processedObjects); 237 analyse(parentStack, object, me.getValue(), processedObjects); 238 } 239 } else { 240 final List<Field> allDeclaredFields = getAllDeclaredFields(object.getClass()); 241 final List<Object> children = new ArrayList<Object>(allDeclaredFields.size()); 242 for (final Field field : allDeclaredFields) { 243 if ((Modifier.STATIC & field.getModifiers()) != 0) 244 continue; 245 246 if ((Modifier.TRANSIENT & field.getModifiers()) != 0) 247 continue; 248 249 final Object child = getFieldValue(object, field); 250 children.add(child); 251 252 logger.debug("analyse: field={} child=", field, child); 253 254 NoObjectRef noObjectRef = field.getAnnotation(NoObjectRef.class); 255 if (noObjectRef == null) 256 noObjectRef = child == null ? null : child.getClass().getAnnotation(NoObjectRef.class); 257 258 if (noObjectRef == null) { 259 final NoObjectRef parentNoObjectRef = object2NoObjectRef.get(object); 260 if (parentNoObjectRef.inheritToObjectGraphChildren()) 261 object2NoObjectRef.put(child, parentNoObjectRef); 262 } 263 else 264 object2NoObjectRef.put(child, noObjectRef); 265 } 266 267 for (final Object child : children) 268 analyse(parentStack, object, child, processedObjects); 269 } 270 } finally { 271 if (parent != null) 272 parentStack.removeLast(); 273 } 274 } 275 276 private Object getFieldValue(final Object object, final Field field) { 277 field.setAccessible(true); 278 try { 279 return field.get(object); 280 } catch (IllegalAccessException e) { 281 throw new RuntimeException(e); 282 } 283 } 284 285 public boolean isNoObjectRef(Object object) { 286 final NoObjectRef result = object2NoObjectRef.get(object); 287 if (result == null) { 288 final NoObjectRef noObjectRef = object.getClass().getAnnotation(NoObjectRef.class); 289 if (noObjectRef != null) 290 return noObjectRef.value(); 291 292 return false; 293 } 294 295 return result.value(); 296 } 297 298 private boolean isTypeConsideredLeaf(final Object object) { 299 final Class<?> clazz = object.getClass(); 300 301 if (ObjectManager.isObjectRefMappingEnabled(object)) 302 return true; 303 304// if (! (object instanceof Serializable)) 305// return true; 306// 307//// if (object instanceof ObjectRef) // does not work, because ObjectRef.ClassInfo.interfaceNames must not be referenced! 308//// return true; 309// 310// if (object instanceof Uid) 311// return true; 312// 313//// if (clazz.isArray() || Collection.class.isAssignableFrom(clazz) ||Map.class.isAssignableFrom(clazz)) 314//// return false; 315 316 // TODO do not hard-code the following, but use an advisor-service! 317 return !clazz.getName().startsWith("co.codewizards.") && !clazz.getName().startsWith("org."); 318 } 319 } 320}