1 /***
2 * Copyright 2009 Martin Vysny.
3 *
4 * This file is part of WebVM.
5 *
6 * WebVM is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * WebVM is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with WebVM. If not, see <http://www.gnu.org/licenses/>.
18 */
19 package sk.baka.webvm;
20
21 import java.io.File;
22 import java.io.Serializable;
23 import java.net.URI;
24 import java.net.URISyntaxException;
25 import java.net.URL;
26 import java.net.URLClassLoader;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.Comparator;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.logging.Level;
34 import java.util.logging.Logger;
35 import javax.swing.tree.DefaultMutableTreeNode;
36 import javax.swing.tree.DefaultTreeModel;
37 import javax.swing.tree.MutableTreeNode;
38 import javax.swing.tree.TreeModel;
39 import org.apache.wicket.ajax.AjaxRequestTarget;
40 import org.apache.wicket.markup.html.basic.Label;
41 import org.apache.wicket.markup.html.list.ListItem;
42 import org.apache.wicket.markup.html.list.ListView;
43 import org.apache.wicket.markup.html.tree.BaseTree;
44 import org.apache.wicket.markup.html.tree.BaseTree.LinkType;
45 import org.apache.wicket.markup.html.tree.ITreeStateListener;
46 import org.apache.wicket.markup.html.tree.LabelTree;
47 import org.apache.wicket.markup.html.tree.LinkTree;
48 import sk.baka.tools.UrlUtils;
49 import sk.baka.webvm.analyzer.classloader.CLEnum;
50 import sk.baka.webvm.analyzer.classloader.ClassLoaderUtils;
51 import sk.baka.webvm.analyzer.classloader.ResourceLink;
52 import sk.baka.webvm.wicket.WicketUtils;
53
54 /***
55 * Performs the class loader analysis.
56 * @author Martin Vysny
57 */
58 public final class Classloaders extends WebVMPage {
59
60 private static final long serialVersionUID = 1L;
61
62 /***
63 * Constructor.
64 */
65 public Classloaders() {
66 tree = newClassloaderHierarchy();
67 analyzeClashes(border);
68 border.add(tree);
69 }
70 private LabelTree tree;
71
72 private void analyzeClashes(AppBorder border) {
73 final Map<URI, List<Integer>> clashes = getClashes();
74 final List<URI> uris = new ArrayList<URI>(clashes.keySet());
75 border.add(new ClassloaderClashes("clRow", uris, clashes));
76 }
77
78 private Map<URI, List<Integer>> getClashes() {
79 final List<ClassLoader> cls = ClassLoaderUtils.getClassLoaderChain(Thread.currentThread().getContextClassLoader());
80 final Map<URI, List<ClassLoader>> clashes;
81 try {
82 clashes = ClassLoaderUtils.getClassLoaderURIs(Thread.currentThread().getContextClassLoader());
83 } catch (URISyntaxException ex) {
84 throw new RuntimeException(ex);
85 }
86 ClassLoaderUtils.filterClashes(clashes);
87 final Map<URI, List<Integer>> result = new HashMap<URI, List<Integer>>();
88 for (final Map.Entry<URI, List<ClassLoader>> e : clashes.entrySet()) {
89 final List<Integer> clNumbers = new ArrayList<Integer>();
90 for (final ClassLoader cl : e.getValue()) {
91 clNumbers.add(cls.indexOf(cl) + 1);
92 }
93 result.put(e.getKey(), clNumbers);
94 }
95 return result;
96 }
97
98 private LabelTree newClassloaderHierarchy() {
99 final MutableTreeNode root = new DefaultMutableTreeNode("root");
100 analyzeClassloader(root, Thread.currentThread().getContextClassLoader());
101 final TreeModel model = new DefaultTreeModel(root);
102 final LinkTree result = new ClassloaderHierarchyTree("classloaderHierarchy", model);
103 result.setRootLess(true);
104
105
106 result.setLinkType(LinkType.REGULAR);
107 result.invalidateAll();
108 result.getTreeState().addTreeStateListener(new Listener());
109 return result;
110 }
111
112 private static void expandNode(final Object node, final DefaultTreeModel tree) {
113 final DefaultMutableTreeNode n = (DefaultMutableTreeNode) node;
114 if (!(n.getUserObject() instanceof ResourceLink)) {
115 return;
116 }
117 final ResourceLink parent = (ResourceLink) n.getUserObject();
118 if (!parent.isPackage()) {
119 return;
120 }
121 n.removeAllChildren();
122 try {
123 final List<ResourceLink> children = parent.listAndGroup();
124 Collections.sort(children, new DirAndNameComparator());
125 for (final ResourceLink link : children) {
126 n.add(new TreeNode(link));
127 }
128 } catch (Exception ex) {
129 LOG.log(Level.SEVERE, "Error while retrieving resources", ex);
130 n.add(new DefaultMutableTreeNode("Error while retrieving resources: " + ex.toString()));
131 }
132 tree.nodeStructureChanged(n);
133 }
134
135 private DefaultTreeModel getModel() {
136 return (DefaultTreeModel) tree.getModelObject();
137 }
138
139 private class Listener implements ITreeStateListener, Serializable {
140
141 private static final long serialVersionUID = 1L;
142
143 public void allNodesCollapsed() {
144
145 }
146
147 public void allNodesExpanded() {
148
149 }
150
151 public void nodeCollapsed(Object node) {
152 final DefaultMutableTreeNode n = (DefaultMutableTreeNode) node;
153 if (!(n.getUserObject() instanceof ResourceLink)) {
154 return;
155 }
156
157 n.removeAllChildren();
158 final DefaultTreeModel model = getModel();
159 model.nodeStructureChanged(n);
160 }
161
162 public void nodeExpanded(Object node) {
163 expandNode(node, getModel());
164 }
165
166 public void nodeSelected(Object node) {
167
168 }
169
170 public void nodeUnselected(Object node) {
171
172 }
173 }
174 private static final Logger LOG = Logger.getLogger(Classloaders.class.getName());
175 private static final int MAX_CLASSLOADER_NAME_LENGTH = 60;
176
177 private static class TreeNode extends DefaultMutableTreeNode {
178
179 private static final long serialVersionUID = 1L;
180
181 public TreeNode(final ResourceLink link) {
182 super(link);
183 }
184
185 @Override
186 public boolean isLeaf() {
187 return !((ResourceLink) getUserObject()).isPackage();
188 }
189 }
190
191 private static void analyzeClassloader(final MutableTreeNode root, final ClassLoader cl) {
192 ClassLoader current = cl;
193 int clNumber = 1;
194 while (current != null) {
195 final DefaultMutableTreeNode result = new DefaultMutableTreeNode();
196 String name = current.toString();
197 if (name.length() > MAX_CLASSLOADER_NAME_LENGTH) {
198 name = name.substring(0, MAX_CLASSLOADER_NAME_LENGTH) + "...";
199 }
200 result.setUserObject("[" + (clNumber++) + "] " + CLEnum.getTypes(current) + " " + current.getClass().getName() + ": " + name);
201 if (current instanceof URLClassLoader) {
202 addClassLoaderURLs(result, (URLClassLoader) current);
203 }
204 root.insert(result, root.getChildCount());
205 current = current.getParent();
206 }
207 }
208
209 private static void addClassLoaderURLs(final DefaultMutableTreeNode result, final URLClassLoader cl) {
210 final URL[] clUrls = cl.getURLs();
211 if (clUrls == null || clUrls.length == 0) {
212 result.add(new DefaultMutableTreeNode("URLClassLoader.getURLs() is empty"));
213 return;
214 }
215 for (final URL url : clUrls) {
216 final File file = toFile(url);
217 final DefaultMutableTreeNode node = (file == null) ? new DefaultMutableTreeNode(url) : new TreeNode(ResourceLink.newFor(file));
218 result.add(node);
219 }
220 }
221
222 /***
223 * Shows class/resource clashes between different classloaders.
224 */
225 private static class ClassloaderClashes extends ListView<URI> {
226
227 private static final long serialVersionUID = 1L;
228 private final Map<URI, List<Integer>> clashes;
229
230 public ClassloaderClashes(String id, List<? extends URI> list, Map<URI, List<Integer>> clashes) {
231 super(id, list);
232 this.clashes = clashes;
233 }
234
235 @Override
236 protected void populateItem(final ListItem<URI> item) {
237 final URI uri = item.getModelObject();
238 item.add(new Label("clURI", uri.toString()));
239 final List<Integer> clNumbers = clashes.get(uri);
240 item.add(new Label("clClassLoader", clNumbers.toString()));
241 }
242 }
243
244 /***
245 * A tree component showing the classloader hierarchy.
246 */
247 private static class ClassloaderHierarchyTree extends LinkTree {
248
249 private static final long serialVersionUID = 1L;
250
251 public ClassloaderHierarchyTree(String id, TreeModel model) {
252 super(id, model);
253 }
254
255 @Override
256 protected void onNodeLinkClicked(Object node, BaseTree tree, AjaxRequestTarget target) {
257 final DefaultMutableTreeNode n = (DefaultMutableTreeNode) node;
258 if (!(n.getUserObject() instanceof ResourceLink)) {
259 tree.getTreeState().expandNode(node);
260 return;
261 }
262 final ResourceLink parent = (ResourceLink) n.getUserObject();
263 final boolean redirected = WicketUtils.redirectTo(parent);
264 if (redirected) {
265 return;
266 }
267 if (parent.isPackage()) {
268 expandNode(node, (DefaultTreeModel) getModelObject());
269 tree.getTreeState().expandNode(node);
270 return;
271 }
272 }
273 }
274
275 /***
276 * Sorts ResourceLinks, packages first, then by a name.
277 */
278 private static class DirAndNameComparator implements Comparator<ResourceLink> {
279
280 public int compare(ResourceLink o1, ResourceLink o2) {
281 if (o1.isPackage()) {
282 if (!o2.isPackage()) {
283 return -1;
284 }
285 } else {
286 if (o2.isPackage()) {
287 return 1;
288 }
289 }
290 return o1.getName().compareToIgnoreCase(o2.getName());
291 }
292 }
293 }