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    }