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
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
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
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
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
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
292 }
293
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
305 if (JeeServer.getRuntimeNull() == JeeServer.Glassfish && name.equals("java:comp/env")) {
306 return "env";
307 }
308 return name;
309 }
310 }