/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.brooklyn.core.mgmt.rebind; import java.io.PrintStream; import java.io.Serializable; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Deque; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; import javax.annotation.Nullable; import org.apache.brooklyn.util.core.flags.FlagUtils; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.javalang.Serializers; import org.apache.brooklyn.util.javalang.Serializers.ObjectReplacer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Objects; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; /** * Convenience for writing out an object hierarchy. * * This is particularly useful for NotSerializableExceptions, where it does not tell you * which object contained the unserializable field. * * @author aled */ public class Dumpers { private static final Logger LOG = LoggerFactory.getLogger(Dumpers.class); private static List<String> UNTRAVERSED_PREFIXES = ImmutableList.of("java.lang", "java.io"); private static final int MAX_MEMBERS = 100; private static final Predicate<Field> SERIALIZED_FIELD_PREDICATE = new Predicate<Field>() { @Override public boolean apply(@Nullable Field input) { int excludedModifiers = Modifier.TRANSIENT ^ Modifier.STATIC; return (input.getModifiers() & excludedModifiers) == 0; } }; public static class Pointer implements Serializable { private static final long serialVersionUID = 1709707205457063174L; private static final Random random = new Random(); private final String id; private final int rand; public Pointer(String id) { this.id = id; this.rand = random.nextInt(); } @Override public int hashCode() { return Objects.hashCode(id, rand); } @Override public boolean equals(Object o) { return (o instanceof Pointer) && Objects.equal(id, ((Pointer)o).id) && Objects.equal(rand, ((Pointer)o).rand); } } public static void logUnserializableChains(Object root) throws IllegalArgumentException, IllegalAccessException { logUnserializableChains(root, ObjectReplacer.NOOP); } public static void logUnserializableChains(Object root, final ObjectReplacer replacer) throws IllegalArgumentException, IllegalAccessException { final Map<List<Object>, Class<?>> unserializablePaths = Maps.newLinkedHashMap(); Visitor visitor = new Visitor() { @Override public boolean visit(Object o, Iterable<Object> refChain) { try { Serializers.reconstitute(o, replacer); return true; } catch (Throwable e) { Exceptions.propagateIfFatal(e); // not serializable in some way: report ImmutableList<Object> refChainList = ImmutableList.copyOf(refChain); // for debugging it can be useful to turn this on // LOG.warn("Unreconstitutable object detected ("+o+"): "+e); // First strip out any less specific paths for (Iterator<List<Object>> iter = unserializablePaths.keySet().iterator(); iter.hasNext();) { List<Object> existing = iter.next(); if (refChainList.size() >= existing.size() && refChainList.subList(0, existing.size()).equals(existing)) { iter.remove(); } } // Then add this list unserializablePaths.put(ImmutableList.copyOf(refChainList), o.getClass()); return false; } } }; deepVisitInternal(root, SERIALIZED_FIELD_PREDICATE, Lists.newArrayList(), new LinkedList<Object>(), visitor); LOG.warn("Not serializable ("+root+"):"); for (Map.Entry<List<Object>, Class<?>> entry : unserializablePaths.entrySet()) { StringBuilder msg = new StringBuilder("\t"+"type="+entry.getValue()+"; chain="+"\n"); for (Object chainElement : entry.getKey()) { // try-catch motivated by NPE in org.jclouds.domain.LoginCredentials.toString String chainElementStr; try { chainElementStr = chainElement.toString(); } catch (Exception e) { Exceptions.propagateIfFatal(e); LOG.error("Error calling toString on instance of "+chainElement.getClass(), e); chainElementStr = "<error "+e.getClass().getSimpleName()+" in toString>"; } msg.append("\t\t"+"type=").append(chainElement.getClass()).append("; val=").append(chainElementStr).append("\n"); } LOG.warn(msg.toString()); } } public static void deepDumpSerializableness(Object o) { deepDump(o, SERIALIZED_FIELD_PREDICATE, System.out); } public static void deepDump(Object o, Predicate<Field> fieldPredicate, PrintStream out) { try { out.println("Deep dump:"); deepDumpInternal(o, fieldPredicate, out, 1, "", Lists.newArrayList()); } catch (Exception e) { throw Throwables.propagate(e); } } private static void deepDumpInternal(Object o, Predicate<Field> fieldPredicate, PrintStream out, int indentSize, String prefix, List<Object> visited) throws IllegalArgumentException, IllegalAccessException { String indent = com.google.common.base.Strings.repeat(" ", indentSize*2); Class<?> clazz = (o != null) ? o.getClass() : null; if (o == null) { out.println(indent+prefix+"null"); } else if (isClassUntraversable(clazz)) { out.println(indent+prefix+"(untraversable) type="+clazz+"; val="+o.toString()); } else if (containsSame(visited, o)) { out.println(indent+prefix+"duplicate (type="+clazz+"; val="+o.toString()+")"); } else { visited.add(o); out.println(indent+prefix+"type="+clazz+"; val="+o.toString()); Map<String, Object> members = findMembers(o, fieldPredicate); for (Map.Entry<String, Object> entry : Iterables.limit(members.entrySet(), MAX_MEMBERS)) { deepDumpInternal(entry.getValue(), fieldPredicate, out, indentSize+1, ""+entry.getKey()+": ", visited); } if (members.size() > MAX_MEMBERS) { out.println(indent+prefix+"TRUNCATED ("+members.size()+" members in total)"); } } } private static void deepVisitInternal(Object o, Predicate<Field> fieldPredicate, List<Object> visited, Deque<Object> refChain, Visitor visitor) throws IllegalArgumentException, IllegalAccessException { Class<?> clazz = (o != null) ? o.getClass() : null; refChain.addLast(o); Iterable<Object> filteredRefChain = Iterables.filter(refChain, Predicates.not(Predicates.instanceOf(Dumpers.Entry.class))); try { if (o == null) { // no-op } else if (isClassUntraversable(clazz)) { visitor.visit(o, filteredRefChain); } else if (containsSame(visited, o)) { // no-op } else { visited.add(o); boolean subTreeComplete = visitor.visit(o, filteredRefChain); if (!subTreeComplete) { Map<String, Object> members = findMembers(o, fieldPredicate); for (Map.Entry<String, Object> entry : members.entrySet()) { deepVisitInternal(entry.getValue(), fieldPredicate, visited, refChain, visitor); } } } } finally { refChain.removeLast(); } } public interface Visitor { /** * @param refChain The chain of references leading to this object (starting at the root) * @return True if this part of the tree is complete; false if need to continue visiting children */ public boolean visit(Object o, Iterable<Object> refChain); } private static Map<String,Object> findMembers(Object o, Predicate<Field> fieldPredicate) throws IllegalArgumentException, IllegalAccessException { Map<String,Object> result = Maps.newLinkedHashMap(); Class<?> clazz = (o != null) ? o.getClass() : null; if (o instanceof Iterable) { int i = 0; for (Object member : (Iterable<?>)o) { result.put("member"+(i++), member); } } else if (o instanceof Map) { int i = 0; Map<?,?> m = (Map<?,?>) o; for (Map.Entry<?,?> entry : m.entrySet()) { result.put("member"+(i++), new Entry(entry.getKey(), entry.getValue())); } } else { for (Field field : FlagUtils.getAllFields(clazz, fieldPredicate)) { field.setAccessible(true); String fieldName = field.getName(); Object fieldVal = field.get(o); result.put(fieldName, fieldVal); } } return result; } private static boolean isClassUntraversable(Class<?> clazz) { String clazzName = clazz.getName(); for (String prefix : UNTRAVERSED_PREFIXES) { if (clazzName.startsWith(prefix)) { return true; } } return false; } private static boolean containsSame(Iterable<?> vals, Object val) { for (Object contender : vals) { if (contender == val) return true; } return false; } private static class Entry implements Serializable { private static final long serialVersionUID = -4751524179224569184L; @SuppressWarnings("unused") final Object key, value; public Entry(Object key, Object value) { this.key = key; this.value = value; } } }