001package org.javasimon.javaee;
002
003import java.lang.reflect.InvocationTargetException;
004import javax.servlet.FilterConfig;
005import javax.servlet.http.HttpServletRequest;
006
007import org.javasimon.Manager;
008import org.javasimon.Stopwatch;
009import org.javasimon.javaee.reqreporter.DefaultRequestReporter;
010import org.javasimon.javaee.reqreporter.RequestReporter;
011import org.javasimon.source.MonitorSource;
012import org.javasimon.source.StopwatchSource;
013import org.javasimon.utils.Replacer;
014import org.javasimon.utils.SimonUtils;
015
016/**
017 * Various supporting utility methods for {@link SimonServletFilter}.
018 *
019 * @author virgo47@gmail.com
020 */
021public class SimonServletFilterUtils {
022        /**
023         * Regex replacer for any number of slashes or dots for a single dot.
024         */
025        private static final Replacer TO_DOT_PATTERN = new Replacer("[/.]+", ".");
026
027        /**
028         * Creates new replacer for unallowed characters in the URL. This inverts character group for name pattern
029         * ({@link SimonUtils#NAME_PATTERN_CHAR_CLASS_CONTENT}) and replaces its dot with slash too (dots are to be
030         * replaced, slashs preserved in this step of URL processing).
031         *
032         * @param replacement replacement string (for every unallowed character)
033         * @return compiled pattern matching characters to remove from the URL
034         */
035        static Replacer createUnallowedCharsReplacer(String replacement) {
036                return new Replacer("[^" + SimonUtils.NAME_PATTERN_CHAR_CLASS_CONTENT.replace('.', '/') + "]+", replacement);
037        }
038
039        /**
040         * Returns Simon name for the specified request (local name without any configured prefix). By default dots and all non-simon-name
041         * compliant characters are removed first, then all slashes are switched to dots (repeating slashes make one dot).
042         *
043         * @param uri request URI
044         * @param unallowedCharacterReplacer replacer for characters that are not allowed in Simon name
045         * @return local part of the Simon name for the request URI (without prefix)
046         */
047        public static String getSimonName(String uri, Replacer unallowedCharacterReplacer) {
048                if (uri.startsWith("/")) {
049                        uri = uri.substring(1);
050                }
051                String name = unallowedCharacterReplacer.process(uri);
052                name = TO_DOT_PATTERN.process(name);
053                return name;
054        }
055
056        /**
057         * Create and initialize the stopwatch source depending on the filter init parameters. Both
058         * monitor source class ({@link SimonServletFilter#INIT_PARAM_STOPWATCH_SOURCE_CLASS} and whether
059         * to cache results ({@link SimonServletFilter#INIT_PARAM_STOPWATCH_SOURCE_CACHE}) can be adjusted.
060         *
061         * @param filterConfig Filter configuration
062         * @return Stopwatch source
063         */
064        protected static StopwatchSource<HttpServletRequest> initStopwatchSource(FilterConfig filterConfig, Manager manager) {
065                String stopwatchSourceClass = filterConfig.getInitParameter(SimonServletFilter.INIT_PARAM_STOPWATCH_SOURCE_CLASS);
066                StopwatchSource<HttpServletRequest> stopwatchSource = createMonitorSource(stopwatchSourceClass, manager);
067
068                injectSimonPrefixIntoMonitorSource(filterConfig, stopwatchSource);
069
070                String cache = filterConfig.getInitParameter(SimonServletFilter.INIT_PARAM_STOPWATCH_SOURCE_CACHE);
071                stopwatchSource = wrapMonitorSourceWithCacheIfNeeded(stopwatchSource, cache);
072
073                return stopwatchSource;
074        }
075
076        private static StopwatchSource<HttpServletRequest> createMonitorSource(String stopwatchSourceClass, Manager manager) {
077                if (stopwatchSourceClass == null) {
078                        return new HttpStopwatchSource(manager);
079                } else {
080                        return createMonitorForSourceSpecifiedClass(stopwatchSourceClass, manager);
081                }
082        }
083
084        private static void injectSimonPrefixIntoMonitorSource(FilterConfig filterConfig, MonitorSource<HttpServletRequest, Stopwatch> stopwatchSource) {
085                String simonPrefix = filterConfig.getInitParameter(SimonServletFilter.INIT_PARAM_PREFIX);
086                if (simonPrefix != null) {
087                        if (stopwatchSource instanceof HttpStopwatchSource) {
088                                HttpStopwatchSource httpStopwatchSource = (HttpStopwatchSource) stopwatchSource;
089                                httpStopwatchSource.setPrefix(simonPrefix);
090                        } else {
091                                throw new IllegalArgumentException("Prefix init param is only compatible with HttpStopwatchSource");
092                        }
093                }
094        }
095
096        private static StopwatchSource<HttpServletRequest> wrapMonitorSourceWithCacheIfNeeded(StopwatchSource<HttpServletRequest> stopwatchSource, String cache) {
097                if (cache != null && Boolean.parseBoolean(cache)) {
098                        stopwatchSource = HttpStopwatchSource.newCacheStopwatchSource(stopwatchSource);
099                }
100                return stopwatchSource;
101        }
102
103        private static StopwatchSource<HttpServletRequest> createMonitorForSourceSpecifiedClass(String stopwatchSourceClass, Manager manager) {
104                try {
105                        Class<?> monitorClass = Class.forName(stopwatchSourceClass);
106                        return  monitorSourceNewInstance(manager, monitorClass);
107                } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | ClassCastException e) {
108                        throw new IllegalArgumentException("Invalid Stopwatch source class name", e);
109                }
110        }
111
112        @SuppressWarnings("unchecked")
113        private static StopwatchSource<HttpServletRequest> monitorSourceNewInstance(Manager manager, Class<?> monitorClass) throws InstantiationException, IllegalAccessException {
114                StopwatchSource<HttpServletRequest> stopwatchSource = null;
115                try {
116                        stopwatchSource = (StopwatchSource<HttpServletRequest>) monitorClass.getConstructor(Manager.class).newInstance(manager);
117                } catch (NoSuchMethodException | InvocationTargetException e) {
118                        // safe to ignore here - we'll try default constructor + setter
119                }
120                if (stopwatchSource == null) {
121                        stopwatchSource = (StopwatchSource<HttpServletRequest>) monitorClass.newInstance();
122                        try {
123                                monitorClass.getMethod("setManager", Manager.class).invoke(stopwatchSource, manager);
124                        } catch (NoSuchMethodException | InvocationTargetException e) {
125                                throw new IllegalArgumentException("Stopwatch source class must have public constructor or public setter with Manager argument (used class " + monitorClass.getName() + ")", e);
126                        }
127                }
128                return stopwatchSource;
129        }
130
131        /**
132         * Returns RequestReporter for the class specified for context parameter {@link SimonServletFilter#INIT_PARAM_REQUEST_REPORTER_CLASS}.
133         */
134        public static RequestReporter initRequestReporter(FilterConfig filterConfig) {
135                String className = filterConfig.getInitParameter(SimonServletFilter.INIT_PARAM_REQUEST_REPORTER_CLASS);
136
137                if (className == null) {
138                        return new DefaultRequestReporter();
139                } else {
140                        try {
141                                return (RequestReporter) Class.forName(className).newInstance();
142                        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException classNotFoundException) {
143                                throw new IllegalArgumentException("Invalid Request reporter class name", classNotFoundException);
144                        }
145                }
146        }
147}