001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.markup.html.debug; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.Comparator; 022import java.util.List; 023 024import org.apache.wicket.Component; 025import org.apache.wicket.MetaDataKey; 026import org.apache.wicket.Page; 027import org.apache.wicket.markup.head.CssHeaderItem; 028import org.apache.wicket.markup.head.IHeaderResponse; 029import org.apache.wicket.markup.html.basic.Label; 030import org.apache.wicket.markup.html.list.ListItem; 031import org.apache.wicket.markup.html.list.ListView; 032import org.apache.wicket.markup.html.panel.Panel; 033import org.apache.wicket.request.resource.CssResourceReference; 034import org.apache.wicket.util.io.IClusterable; 035import org.apache.wicket.util.lang.Bytes; 036import org.apache.wicket.util.string.Strings; 037import org.apache.wicket.util.visit.IVisit; 038import org.apache.wicket.util.visit.IVisitor; 039 040 041/** 042 * This is a simple Wicket component that displays all components of a Page in a table 043 * representation. Useful for debugging. 044 * <p> 045 * Simply add this code to your page's constructor: 046 * 047 * <pre> 048 * add(new PageView("componentTree", this)); 049 * </pre> 050 * 051 * And this to your markup: 052 * 053 * <pre> 054 * <span wicket:id="componentTree"/> 055 * </pre> 056 * 057 * @author Juergen Donnerstag 058 */ 059public final class PageView extends Panel 060{ 061 /** 062 * A meta data key used by RenderPerformaceListener in wicket-devutils to collect the time 063 * needed by a component to render itself 064 */ 065 public static final MetaDataKey<Long> RENDER_KEY = new MetaDataKey<>() 066 { 067 }; 068 069 /** 070 * El cheapo data holder. 071 * 072 * @author Juergen Donnerstag 073 */ 074 private static class ComponentData implements IClusterable 075 { 076 private static final long serialVersionUID = 1L; 077 078 /** Component path. */ 079 public final String path; 080 081 /** Component type. */ 082 public final String type; 083 084 /** Component value. */ 085 public String value; 086 087 /** Size of component in bytes */ 088 public final long size; 089 090 /** the time it took to rended the component */ 091 private Long renderDuration; 092 093 ComponentData(String path, String type, long size) 094 { 095 this.path = path; 096 this.type = type; 097 this.size = size; 098 } 099 } 100 101 private static final long serialVersionUID = 1L; 102 103 /** 104 * Constructor. 105 * 106 * @param id 107 * See Component 108 * @param page 109 * The page to be analyzed 110 * @see Component#Component(String) 111 */ 112 public PageView(final String id, final Page page) 113 { 114 super(id); 115 116 // Name of page 117 add(new Label("info", page == null ? "[Stateless Page]" : page.toString())); 118 119 // Create an empty list. It'll be filled later 120 List<ComponentData> data = null; 121 122 String pageRenderDuration = "n/a"; 123 124 if (page != null) 125 { 126 Long renderTime = page.getMetaData(RENDER_KEY); 127 if (renderTime != null) 128 { 129 pageRenderDuration = renderTime.toString(); 130 } 131 132 // Get the components data and fill and sort the list 133 data = new ArrayList<ComponentData>(getComponentData(page)); 134 Collections.sort(data, new Comparator<ComponentData>() 135 { 136 @Override 137 public int compare(ComponentData o1, ComponentData o2) 138 { 139 return (o1).path.compareTo((o2).path); 140 } 141 }); 142 } 143 else 144 { 145 data = Collections.emptyList(); 146 } 147 148 add(new Label("pageRenderDuration", pageRenderDuration)); 149 150 151 // Create the table containing the list the components 152 add(new ListView<ComponentData>("components", data) 153 { 154 private static final long serialVersionUID = 1L; 155 156 /** 157 * Populate the table with Wicket elements 158 */ 159 @Override 160 protected void populateItem(final ListItem<ComponentData> listItem) 161 { 162 final ComponentData componentData = listItem.getModelObject(); 163 164 listItem.add(new Label("row", Long.toString(listItem.getIndex() + 1))); 165 listItem.add(new Label("path", componentData.path)); 166 listItem.add(new Label("size", Bytes.bytes(componentData.size).toString())); 167 listItem.add(new Label("type", componentData.type)); 168 listItem.add(new Label("model", componentData.value)); 169 listItem.add(new Label("renderDuration", componentData.renderDuration != null 170 ? componentData.renderDuration.toString() : "n/a")); 171 } 172 }); 173 } 174 175 /** 176 * Get recursively all components of the page, extract the information relevant for us and add 177 * them to a list. 178 * 179 * @param page 180 * @return List of component data objects 181 */ 182 private List<ComponentData> getComponentData(final Page page) 183 { 184 final List<ComponentData> data = new ArrayList<ComponentData>(); 185 186 page.visitChildren(new IVisitor<Component, Void>() 187 { 188 @Override 189 public void component(final Component component, final IVisit<Void> visit) 190 { 191 if (!component.getPath().startsWith(PageView.this.getPath())) 192 { 193 final ComponentData componentData; 194 195 // anonymous class? Get the parent's class name 196 String name = component.getClass().getName(); 197 if (name.indexOf("$") > 0) 198 { 199 name = component.getClass().getSuperclass().getName(); 200 } 201 202 // remove the path component 203 name = Strings.lastPathComponent(name, Component.PATH_SEPARATOR); 204 205 componentData = new ComponentData(component.getPageRelativePath(), name, 206 component.getSizeInBytes()); 207 208 Long renderDuration = component.getMetaData(RENDER_KEY); 209 if (renderDuration != null) 210 { 211 componentData.renderDuration = renderDuration; 212 } 213 214 try 215 { 216 componentData.value = component.getDefaultModelObjectAsString(); 217 } 218 catch (Exception e) 219 { 220 componentData.value = e.getMessage(); 221 } 222 223 data.add(componentData); 224 } 225 226 } 227 }); 228 229 return data; 230 } 231 232 @Override 233 public void renderHead(IHeaderResponse response) 234 { 235 super.renderHead(response); 236 response.render( 237 CssHeaderItem.forReference(new CssResourceReference(PageView.class, "pageview.css"))); 238 } 239}