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(&quot;componentTree&quot;, this));
049 * </pre>
050 * 
051 * And this to your markup:
052 * 
053 * <pre>
054 *  &lt;span wicket:id=&quot;componentTree&quot;/&gt;
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}