View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.monitoring.reporting.web;
18  
19  import org.apache.commons.monitoring.reporting.web.handler.FilteringEndpoints;
20  import org.apache.commons.monitoring.reporting.web.handler.HomeEndpoint;
21  import org.apache.commons.monitoring.reporting.web.handler.internal.EndpointInfo;
22  import org.apache.commons.monitoring.reporting.web.handler.internal.Invoker;
23  import org.apache.commons.monitoring.reporting.web.plugin.PluginRepository;
24  import org.apache.commons.monitoring.reporting.web.template.MapBuilder;
25  import org.apache.commons.monitoring.reporting.web.template.Templates;
26  
27  import javax.servlet.Filter;
28  import javax.servlet.FilterChain;
29  import javax.servlet.FilterConfig;
30  import javax.servlet.ServletException;
31  import javax.servlet.ServletRequest;
32  import javax.servlet.ServletResponse;
33  import javax.servlet.http.HttpServletRequest;
34  import javax.servlet.http.HttpServletResponse;
35  import java.io.ByteArrayInputStream;
36  import java.io.ByteArrayOutputStream;
37  import java.io.IOException;
38  import java.io.InputStream;
39  import java.io.PrintStream;
40  import java.io.PrintWriter;
41  import java.io.StringWriter;
42  import java.lang.reflect.InvocationHandler;
43  import java.lang.reflect.Method;
44  import java.lang.reflect.Proxy;
45  import java.util.HashMap;
46  import java.util.Map;
47  import java.util.regex.Matcher;
48  import java.util.regex.Pattern;
49  
50  public class MonitoringController implements Filter {
51      private final Map<String, byte[]> cachedResources = new HashMap<String, byte[]>();
52      private final Map<Pattern, Invoker> invokers = new HashMap<Pattern, Invoker>();
53      private String mapping;
54      private ClassLoader classloader;
55      private Invoker defaultInvoker;
56  
57      @Override
58      public void init(final FilterConfig config) throws ServletException {
59          classloader = Thread.currentThread().getContextClassLoader();
60          initMapping(config);
61          initHandlers();
62          Templates.init(config.getServletContext().getContextPath(), mapping);
63      }
64  
65      private void initHandlers() {
66          // home page
67          invokers.putAll(EndpointInfo.build(HomeEndpoint.class, null, "").getInvokers());
68          defaultInvoker = invokers.values().iterator().next();
69  
70          // filtered to get the right base for pictures
71          invokers.putAll(EndpointInfo.build(FilteringEndpoints.class, null, "").getInvokers());
72  
73          // plugins
74          for (final PluginRepository.PluginInfo plugin : PluginRepository.PLUGIN_INFO) {
75              for (final Map.Entry<Pattern, Invoker> invoker : plugin.getInvokers().entrySet()) {
76                  invokers.put(invoker.getKey(), invoker.getValue());
77              }
78          }
79      }
80  
81      private void initMapping(FilterConfig config) {
82          mapping = config.getInitParameter("monitoring-mapping");
83          if (mapping == null) {
84              mapping = "";
85          } else if (!mapping.startsWith("/")) {
86              mapping = "/" + mapping;
87          }
88          if (mapping.endsWith("/")) {
89              mapping = mapping.substring(0, mapping.length() - 1);
90          }
91      }
92  
93      @Override
94      public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
95          if (!HttpServletRequest.class.isInstance(request)) {
96              chain.doFilter(request, response);
97              return;
98          }
99  
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 }