package de.flower.common.ui.serialize; import com.google.common.collect.Lists; import com.google.common.collect.Multiset; import com.thoughtworks.xstream.XStream; import de.flower.common.util.xstream.ClassEmittingReflectionConverter; import de.flower.common.util.xstream.ObjectSerializationListener; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; /** * Monitors serialized pages and checks for serialized entities. * * @author flowerrrr */ @Component public class PageSerializationValidatorListener implements ISerializerListener { public static class PageSerializationException extends RuntimeException { public PageSerializationException(final String message) { super(message); } } private final static Logger log = LoggerFactory.getLogger(PageSerializationValidatorListener.class); private final static Logger xstreamLog = LoggerFactory.getLogger("xstream"); private XStream xstream; private Filter filter; @Autowired public PageSerializationValidatorListener(Filter filter) { xstream = new XStream(); // by default xstream does not output the classname of serialized fields. ClassEmittingReflectionConverter converter = new ClassEmittingReflectionConverter(xstream); final ObjectSerializationListener objectSerializationListener = new ObjectSerializationListener(); converter.setListener(objectSerializationListener); xstream.registerConverter(converter, XStream.PRIORITY_VERY_LOW); this.filter = filter; } @Override public void notify(Object object, byte[] data) { ObjectSerializationListener.reset(); final String xml = xstream.toXML(object); if (data != null) { long length = data.length; log.info("Size of serialized page: " + (length / 1024) + " KB."); } xstreamLog.trace(xml); ObjectSerializationListener.Context context = ObjectSerializationListener.getContext(); checkSerializedObjects(context); ObjectSerializationListener.reset(); } /** * <pre> * class on whitelist -> ok * class on blacklist -> exception * otherwise -> log.warn * </pre> */ private void checkSerializedObjects(ObjectSerializationListener.Context context) { List<String> blackListed = Lists.newArrayList(); List<String> undefinedList = Lists.newArrayList(); for (Multiset.Entry<Class<?>> entry : context.typeSet.entrySet()) { String className = entry.getElement().getName(); switch (filter.matches(className)) { case WHITELIST: break; case BLACKLIST: blackListed.add(className); break; default: undefinedList.add(className); } } if (!undefinedList.isEmpty()) { log.warn("Unknown classes. Consider adding them to white/blacklist:\n{}", StringUtils.join(undefinedList, "\n")); } if (!blackListed.isEmpty()) { log.error("Blacklisted serialized classes in page: {}", blackListed); log.error("Turn on TRACE level for 'xstream' logger and check serialized xml output for violating objects."); throw new PageSerializationException("Serialized domain objects in your wicket pages! Use AbstractEntityModels instead."); } } public void setFilter(final Filter filter) { this.filter = filter; } }