View Javadoc

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         // resource download does not work with AJAX links
105         // @TODO fix this - provide regular links for downloads, ajax links for anything else
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             // do nothing
145         }
146 
147         public void allNodesExpanded() {
148             // do nothing
149         }
150 
151         public void nodeCollapsed(Object node) {
152             final DefaultMutableTreeNode n = (DefaultMutableTreeNode) node;
153             if (!(n.getUserObject() instanceof ResourceLink)) {
154                 return;
155             }
156             // cleanup children
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             // do nothing
168         }
169 
170         public void nodeUnselected(Object node) {
171             // do nothing
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 }