/** * Copyright (C) 2011 * Michael Mosmann <michael@mosmann.de> * Jan Bernitt <unknown@email.de> * * with contributions from * nobody yet * * Licensed 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 de.flapdoodle.wicket.detach; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import org.apache.wicket.Component; import org.apache.wicket.IDetachListener; import org.apache.wicket.Page; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import de.flapdoodle.wicket.serialize.java.PreSerializeChecker; /** * some trouble with listview * @author mosmann * */ public class FieldInspectingDetachListener implements IDetachListener { private static final Logger log = LoggerFactory.getLogger(FieldInspectingDetachListener.class); @Override public void onDetach(Component component) { Class<? extends Component> clazz = component.getClass(); if (false) log.debug("detaching {} ", typeName(clazz)); List<Field> fields = getAllFields(clazz); Map<Object,Boolean> hitMap=new IdentityHashMap<Object, Boolean>(); checkFields(typeName(clazz)+"[path="+component.getPath()+"]", hitMap, component, fields); } private String typeName(Class<? extends Component> clazz) { return "" + clazz.getName() + (clazz.isAnonymousClass() ? "("+clazz.getSuperclass().getName()+")" : ""); } private void checkFields(String id, Map<Object,Boolean> hitMap, Object component, List<Field> fields) { for (Field f : fields) { if (isWorthALook(f)) { try { if (false) log.debug("inspect {}",f); f.setAccessible(true); Object value = f.get(component); if (!hitMap.containsKey(value)) { hitMap.put(value, true); if (value!=null) { if (value instanceof LoadableDetachableModel) { LoadableDetachableModel ldm=(LoadableDetachableModel) value; if (ldm.isAttached()) { log.error(id+": loadable model "+ldm+" in "+f+" is NOT detached ",new RuntimeException()); } } checkFields(id,hitMap, value, getAllFields(value.getClass())); } } } catch (IllegalArgumentException e) { log.error("get value",e); } catch (IllegalAccessException e) { log.error("get value",e); } } } if (component instanceof Component) { Component c=(Component) component; IModel<?> model = c.getDefaultModel(); if (model!=null) { checkFields(id,hitMap,model,getAllFields(model.getClass())); } } } private boolean isWorthALook(Field f) { Class<?> fieldType = f.getType(); if (fieldType.isPrimitive()) return false; Package fieldPackage = fieldType.getPackage(); if (fieldPackage!=null) { if (fieldPackage.getName().startsWith("java.lang.")) return false; } if (Page.class.isAssignableFrom(fieldType)) return false; if (f.getDeclaringClass()==Component.class) { if (f.getName().equals("parent")) { return false; } if (f.getName().equals("data")) { return false; } } if (Modifier.isStatic(f.getModifiers())) return false; return true; } private List<Field> getAllFields(Class<?> clazz) { List<Field> fields=new ArrayList<Field>(); fields.addAll(Arrays.asList(clazz.getDeclaredFields())); Class<?> superclass = clazz.getSuperclass(); if (superclass!=null) { fields.addAll(getAllFields(superclass)); } return fields; } @Override public void onDestroyListener() { } }