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; 018 019 import org.apache.commons.monitoring.reporting.web.handler.FilteringEndpoints; 020 import org.apache.commons.monitoring.reporting.web.handler.HomeEndpoint; 021 import org.apache.commons.monitoring.reporting.web.handler.internal.EndpointInfo; 022 import org.apache.commons.monitoring.reporting.web.handler.internal.Invoker; 023 import org.apache.commons.monitoring.reporting.web.plugin.PluginRepository; 024 import org.apache.commons.monitoring.reporting.web.template.MapBuilder; 025 import org.apache.commons.monitoring.reporting.web.template.Templates; 026 027 import javax.servlet.Filter; 028 import javax.servlet.FilterChain; 029 import javax.servlet.FilterConfig; 030 import javax.servlet.ServletException; 031 import javax.servlet.ServletRequest; 032 import javax.servlet.ServletResponse; 033 import javax.servlet.http.HttpServletRequest; 034 import javax.servlet.http.HttpServletResponse; 035 import java.io.ByteArrayInputStream; 036 import java.io.ByteArrayOutputStream; 037 import java.io.IOException; 038 import java.io.InputStream; 039 import java.io.PrintStream; 040 import java.io.PrintWriter; 041 import java.io.StringWriter; 042 import java.lang.reflect.InvocationHandler; 043 import java.lang.reflect.Method; 044 import java.lang.reflect.Proxy; 045 import java.util.HashMap; 046 import java.util.Map; 047 import java.util.regex.Matcher; 048 import java.util.regex.Pattern; 049 050 public class MonitoringController implements Filter { 051 private final Map<String, byte[]> cachedResources = new HashMap<String, byte[]>(); 052 private final Map<Pattern, Invoker> invokers = new HashMap<Pattern, Invoker>(); 053 private String mapping; 054 private ClassLoader classloader; 055 private Invoker defaultInvoker; 056 057 @Override 058 public void init(final FilterConfig config) throws ServletException { 059 classloader = Thread.currentThread().getContextClassLoader(); 060 initMapping(config); 061 initHandlers(); 062 Templates.init(config.getServletContext().getContextPath(), mapping); 063 } 064 065 private void initHandlers() { 066 // home page 067 invokers.putAll(EndpointInfo.build(HomeEndpoint.class, null, "").getInvokers()); 068 defaultInvoker = invokers.values().iterator().next(); 069 070 // filtered to get the right base for pictures 071 invokers.putAll(EndpointInfo.build(FilteringEndpoints.class, null, "").getInvokers()); 072 073 // plugins 074 for (final PluginRepository.PluginInfo plugin : PluginRepository.PLUGIN_INFO) { 075 for (final Map.Entry<Pattern, Invoker> invoker : plugin.getInvokers().entrySet()) { 076 invokers.put(invoker.getKey(), invoker.getValue()); 077 } 078 } 079 } 080 081 private void initMapping(FilterConfig config) { 082 mapping = config.getInitParameter("monitoring-mapping"); 083 if (mapping == null) { 084 mapping = ""; 085 } else if (!mapping.startsWith("/")) { 086 mapping = "/" + mapping; 087 } 088 if (mapping.endsWith("/")) { 089 mapping = mapping.substring(0, mapping.length() - 1); 090 } 091 } 092 093 @Override 094 public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { 095 if (!HttpServletRequest.class.isInstance(request)) { 096 chain.doFilter(request, response); 097 return; 098 } 099 100 final HttpServletRequest httpRequest = HttpServletRequest.class.cast(request); 101 final HttpServletResponse httpResponse = HttpServletResponse.class.cast(response); 102 103 String path = httpRequest.getRequestURI().substring(httpRequest.getContextPath().length() + mapping.length()); 104 if (!path.startsWith("/")) { 105 path = "/" + path; 106 } 107 108 // find the matching invoker 109 Invoker invoker = defaultInvoker; 110 Matcher matcher = null; 111 for (final Map.Entry<Pattern, Invoker> entry : invokers.entrySet()) { 112 matcher = entry.getKey().matcher(path); 113 if (matcher.matches()) { 114 invoker = entry.getValue(); 115 break; 116 } 117 } 118 119 // resource, they are in the classloader and not in the webapp to ease the embedded case 120 if (path.startsWith("/resources/")) { 121 byte[] bytes = cachedResources.get(path); 122 if (bytes == null) { 123 final InputStream is; 124 if (invoker != defaultInvoker) { // resource is filtered so filtering it before caching it 125 final StringWriter writer = new StringWriter(); 126 final PrintWriter printWriter = new PrintWriter(writer); 127 invoker.invoke(httpRequest, HttpServletResponse.class.cast(Proxy.newProxyInstance(classloader, new Class<?>[] { HttpServletResponse.class }, new InvocationHandler() { 128 @Override 129 public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { 130 if ("getWriter".equals(method.getName())) { 131 return printWriter; 132 } 133 return method.invoke(httpResponse, args); 134 } 135 })), null); 136 is = new ByteArrayInputStream(writer.toString().getBytes()); 137 } else { 138 is = classloader.getResourceAsStream(path.substring(1)); 139 } 140 141 if (is != null) { 142 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 143 int i; 144 while ((i = is.read()) != -1) { 145 baos.write(i); 146 } 147 148 bytes = baos.toByteArray(); 149 cachedResources.put(path, bytes); 150 } 151 } 152 if (bytes != null) { 153 httpResponse.getOutputStream().write(bytes); 154 return; 155 } 156 } 157 158 // delegate handling to the invoker if request is not a resource 159 if (invoker == null) { 160 error(response, null); 161 } else { 162 try { 163 invoker.invoke(httpRequest, httpResponse, matcher); 164 } catch (final Exception e) { 165 error(response, e); 166 } 167 } 168 } 169 170 private void error(final ServletResponse response, final Exception e) throws IOException { 171 final String exception; 172 if (e != null) { 173 final ByteArrayOutputStream err = new ByteArrayOutputStream(); 174 e.printStackTrace(new PrintStream(err)); 175 exception = new String(err.toByteArray()); 176 } else { 177 exception = "No matcher found"; 178 } 179 Templates.htmlRender(response.getWriter(), "error.vm", new MapBuilder<String, Object>().set("exception", exception).build()); 180 } 181 182 @Override 183 public void destroy() { 184 invokers.clear(); 185 } 186 }