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     */
017    package org.apache.commons.monitoring.reporting.web.plugin.thread;
018    
019    import org.apache.commons.codec.binary.Base64;
020    import org.apache.commons.lang3.StringEscapeUtils;
021    import org.apache.commons.monitoring.reporting.web.handler.api.Regex;
022    import org.apache.commons.monitoring.reporting.web.handler.api.Template;
023    import org.apache.commons.monitoring.reporting.web.template.MapBuilder;
024    
025    import java.util.Map;
026    import java.util.TreeMap;
027    
028    public class ThreadEndpoints {
029        @Regex
030        public Template home() {
031            return new Template("threads/threads.vm", new MapBuilder<String, Object>()
032                                    .set("threads", listThreads())
033                                    .build());
034        }
035    
036        @Regex("/([^/]*)")
037        public Template dump(final String name) {
038            final Thread thread = findThread(new String(Base64.decodeBase64(name)));
039            if (thread == null) {
040                return new Template("templates/threads/thread.vm", new MapBuilder<String, Object>().set("state", "Not found").build(), false);
041            }
042    
043            return new Template(
044                    "templates/threads/thread.vm",
045                    new MapBuilder<String, Object>().set("state", thread.getState().name()).set("dump", StringEscapeUtils.escapeHtml4(dump(thread))).build(),
046                    false);
047        }
048    
049        private static Thread findThread(final String name) {
050            int count = Thread.activeCount();
051            final Thread[] threads = new Thread[count];
052            count = Thread.enumerate(threads);
053    
054            for (int i = 0; i < count; i++) {
055                if (threads[i].getName().equals(name)) {
056                    return threads[i];
057                }
058            }
059    
060            return null;
061        }
062    
063        private static String dump(final Thread thread) {
064            final StackTraceElement[] stack = thread.getStackTrace();
065            final StringBuilder builder = new StringBuilder();
066            for (final StackTraceElement element : stack) {
067                builder.append(element.toString()).append("\n"); // toString method is fine
068            }
069            return builder.toString();
070        }
071    
072        private static Map<String, String> listThreads() {
073            final Map<String, String> out = new TreeMap<String, String>();
074    
075            int count = Thread.activeCount();
076            final Thread[] threads = new Thread[count];
077            count = Thread.enumerate(threads);
078    
079            for (int i = 0; i < count; i++) {
080                final String name = threads[i].getName();
081                out.put(name, Base64.encodeBase64String(name.getBytes()));
082            }
083    
084            return out;
085        }
086    }