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.lang.reflect.Proxy;
22  import java.util.Collections;
23  import java.util.Comparator;
24  import java.util.List;
25  import java.util.logging.Level;
26  import java.util.logging.Logger;
27  import javax.naming.Context;
28  import javax.naming.InitialContext;
29  import javax.naming.LinkRef;
30  import javax.naming.NameClassPair;
31  import javax.naming.NamingEnumeration;
32  import javax.naming.NamingException;
33  import javax.swing.tree.DefaultMutableTreeNode;
34  import javax.swing.tree.DefaultTreeModel;
35  import javax.swing.tree.MutableTreeNode;
36  import javax.swing.tree.TreeModel;
37  import javax.swing.tree.TreeNode;
38  import org.apache.wicket.markup.html.tree.LabelTree;
39  import org.apache.wicket.model.LoadableDetachableModel;
40  import sk.baka.tools.javaee.JeeServer;
41  
42  /***
43   * Shows the JNDI tree.
44   * @author Martin Vysny
45   */
46  public final class Jndi extends WebVMPage {
47  
48      private static final long serialVersionUID = 1L;
49  
50      /***
51       * Constructor.
52       */
53      public Jndi() {
54          border.add(newJndiTree("jndiTree", null));
55          border.add(newJndiTree("jndiJavaTree", "java:comp"));
56      }
57  
58      private static LabelTree newJndiTree(final String treeId, final String context) {
59          final LabelTree tree = new LabelTree(treeId, new LoadableDetachableModel<TreeModel>() {
60  
61              private static final long serialVersionUID = 1L;
62  
63              @Override
64              protected TreeModel load() {
65                  try {
66                      Context c = new InitialContext();
67                      if (context != null) {
68                          c = (Context) c.lookup(context);
69                      }
70                      return listJNDI(c);
71                  } catch (Exception ex) {
72                      return toModel(ex);
73                  }
74              }
75          });
76          tree.getTreeState().expandAll();
77          tree.setRootLess(true);
78          return tree;
79      }
80  
81      private static TreeModel toModel(Exception ex) {
82          final TreeNode root = new DefaultMutableTreeNode("Error listing JNDI tree: " + ex.toString());
83          return new DefaultTreeModel(root);
84      }
85  
86      /***
87       * Lists the JNDI context and returns it as a tree.
88       * @param c the context to list
89       * @return JNDI pretty-printed JNDI tree
90       */
91      public static TreeModel listJNDI(final Context c) {
92          final MutableTreeNode root = new DefaultMutableTreeNode("root");
93          list(c, root, 0);
94          return new DefaultTreeModel(root);
95      }
96  
97      /***
98       * Prints the context recursively.
99       *
100      * @param ctx
101      *            the context contents to print.
102      * @param parent print children of this parent.
103      * @param depth current depth.
104      */
105     private static void list(final Context ctx, final MutableTreeNode parent, final int depth) {
106         try {
107             final NamingEnumeration<NameClassPair> ne = ctx.list("");
108             try {
109                 // sort the JNDI listing first
110                 final List<NameClassPair> children = Collections.list(ne);
111                 Collections.sort(children, new Comparator<NameClassPair>() {
112 
113                     public int compare(NameClassPair o1, NameClassPair o2) {
114                         return o1.getName().compareToIgnoreCase(o2.getName());
115                     }
116                 });
117                 for (final NameClassPair pair : children) {
118                     try {
119                         listUnprotected(ctx, parent, pair, depth);
120                     } catch (final Exception e) {
121                         LOG.log(Level.SEVERE, "JNDI examination error", e);
122                         final StringBuilder sb = new StringBuilder();
123                         sb.append(pair.getName());
124                         sb.append(": Failed to examine: ");
125                         sb.append(e.toString());
126                         addWarningNode(parent, sb.toString());
127                     }
128                 }
129             } finally {
130                 closeQuietly(ne);
131             }
132         } catch (Exception ex) {
133             LOG.log(Level.SEVERE, "JNDI examination error", ex);
134             String name = "[unknown]";
135             try {
136                 name = ctx.getNameInNamespace();
137             } catch (Exception e) {
138                 // do nothing
139             }
140             addWarningNode(parent, name + ": Failed to examine: " + ex.toString());
141         }
142     }
143     /***
144      * Enumerates the JNDI tree up to this depth.
145      */
146     private static final int MAX_DEPTH = 5;
147 
148     private static void listUnprotected(final Context ctx, final MutableTreeNode parent, final NameClassPair pair, final int depth)
149             throws NamingException, ClassNotFoundException {
150         final JndiTreeNode node = new JndiTreeNode(ctx, pair);
151         if (node.isContext) {
152             //sometimes there is an StackOverflow exception, we rather check it with primitive condition
153             if (depth < MAX_DEPTH) {
154                 final Object value = ctx.lookup(jndiFix(pair.getName()));
155                 final Context subctx = (Context) value;
156                 list(subctx, node, depth + 1);
157             } else {
158                 addWarningNode(node, "Maximum depth of " + MAX_DEPTH + " reached");
159             }
160         }
161         parent.insert(node, parent.getChildCount());
162     }
163 
164     private static void addWarningNode(final MutableTreeNode node,
165             final String text) {
166         node.insert(new DefaultMutableTreeNode(text), node.getChildCount());
167     }
168 
169     /***
170      * Closes given object quietly. Any exceptions are logged using debug level.
171      *
172      * @param c
173      *            the object to close.
174      */
175     public static void closeQuietly(final NamingEnumeration<?> c) {
176         try {
177             c.close();
178         } catch (final Exception ex) {
179             LOG.log(Level.FINE, "Failed to close an object", ex);
180         }
181     }
182     private static final Logger LOG = Logger.getLogger(Jndi.class.getName());
183 
184     /***
185      * A model object which constructs itself from given JNDI information.
186      */
187     private static class JndiTreeNode extends DefaultMutableTreeNode {
188 
189         private static final long serialVersionUID = 1L;
190         /***
191          * true if given nameclass pair is a context which may contain other pairs.
192          */
193         private boolean isContext = false;
194         /***
195          * true if given nameclass pair is a reference object.
196          */
197         private boolean isLinkRef = false;
198         /***
199          * true if given nameclass pair is a proxy object.
200          */
201         private boolean isProxy = false;
202         /***
203          * true if given nameclass pair class was not found.
204          */
205         private Throwable classLoadFailure = null;
206 
207         /***
208          * Creates new node model object from given JNDI pair.
209          * @param ctx the context instance
210          * @param pair the name class pair
211          * @throws javax.naming.NamingException
212          */
213         public JndiTreeNode(final Context ctx, final NameClassPair pair) throws NamingException {
214             super();
215             final StringBuilder sb = new StringBuilder();
216             final String name = jndiFix(pair.getName());
217             sb.append(name);
218             Class<?> clazz = null;
219             try {
220                 clazz = loadClass(pair, ctx);
221                 isContext = Context.class.isAssignableFrom(clazz);
222                 isLinkRef = LinkRef.class.isAssignableFrom(clazz);
223                 isProxy = Proxy.isProxyClass(clazz) || pair.getClassName().startsWith("$Proxy");
224             } catch (ClassNotFoundException ex) {
225                 classLoadFailure = ex;
226             }
227             // Display reference targets
228             if (isLinkRef) {
229                 final Object obj = ctx.lookupLink(name);
230                 final LinkRef link = (LinkRef) obj;
231                 sb.append("[link -> ");
232                 sb.append(link.getLinkName());
233                 sb.append(']');
234             }
235             // Display proxy interfaces
236             if (isProxy) {
237                 sb.append(" (proxy: " + pair.getClassName());
238                 final Class<?>[] ifaces = clazz.getInterfaces();
239                 sb.append(" implements ");
240                 for (int i = 0; i < ifaces.length; i++) {
241                     sb.append(ifaces[i]);
242                     sb.append(',');
243                 }
244                 sb.setCharAt(sb.length() - 1, ')');
245             } else if (isContext) {
246                 sb.append(" (Context)");
247             } else {
248                 sb.append(" (class: ");
249                 sb.append(pair.getClassName());
250                 sb.append(")");
251             }
252             if (classLoadFailure != null) {
253                 sb.append(" - failed to load class: ");
254                 sb.append(classLoadFailure.toString());
255             } else {
256                 if (String.class.isAssignableFrom(clazz)) {
257                     sb.append(": ");
258                     final String str = (String) ctx.lookup(name);
259                     if (str == null) {
260                         sb.append("null");
261                     } else {
262                         sb.append("\"");
263                         sb.append(str);
264                         sb.append("\"");
265                     }
266                 }
267             }
268             setUserObject(sb.toString());
269         }
270 
271         /***
272          * Try to get the class from given JNDI pair.
273          *
274          * @param pair
275          *            the pair
276          * @param ctx
277          *            the context
278          * @return non-<code>null</code> class instance.
279          * @throws ClassNotFoundException
280          * @throws NamingException
281          */
282         private static Class<?> loadClass(final NameClassPair pair,
283                 final Context ctx) throws ClassNotFoundException, NamingException {
284             final ClassLoader loader = Thread.currentThread().getContextClassLoader();
285             try {
286                 return loader.loadClass(pair.getClassName());
287             } catch (ClassNotFoundException e) {
288                 if (!pair.getClassName().startsWith("$Proxy")) {
289                     throw e;
290                 }
291             // try some other methods of obtaining the class.
292             }
293             // We have to get the class from the binding
294             final Object p = ctx.lookup(pair.getName());
295             return p.getClass();
296         }
297 
298         @Override
299         public boolean isLeaf() {
300             return !isContext;
301         }
302     }
303     static String jndiFix(String name) {
304         // workaround: Glassfish returns java:comp/env as a single child of java:comp. It should return just env instead. Fix that.
305         if (JeeServer.getRuntimeNull() == JeeServer.Glassfish && name.equals("java:comp/env")) {
306             return "env";
307         }
308         return name;
309     }
310 }