/*
 * Copyright 2015 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package io.netty.handler.codec.http.router;

import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.QueryStringDecoder;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * Router that contains information about both route matching orders and
 * HTTP request methods.
 *
 *
Routes are devided into 3 sections: "first", "last", and "other".
 * Routes in "first" are matched first, then in "other", then in "last".
 *
 *
Create router

 *
 *
Route targets can be any type. In the below example, targets are classes:
 *
 *

 * {@code
 * Router<Class> router = new Router<Class>()
 *   .GET      ("/articles",     IndexHandler.class)
 *   .GET      ("/articles/:id", ShowHandler.class)
 *   .POST     ("/articles",     CreateHandler.class)
 *   .GET      ("/download/:*",  DownloadHandler.class)  // ":*" must be the last token
 *   .GET_FIRST("/articles/new", NewHandler.class);      // This will be matched first
 * }
 *

 *
 *
Slashes at both ends are ignored. These are the same:
 *
 *

 * {@code
 * router.GET("articles",   IndexHandler.class);
 * router.GET("/articles",  IndexHandler.class);
 * router.GET("/articles/", IndexHandler.class);
 * }
 *

 *
 *
You can remove routes by target or by path pattern:
 *
 *

 * {@code
 * router.removeTarget(IndexHandler.class);
 * router.removePathPattern("/articles");
 * }
 *

 *
 *
Match with request method and URI

 *
 *
Use {@link #route(HttpMethod, String)}.
 *
 *
From the {@link RouteResult} you can extract params embedded in
 * the path and from the query part of the request URI.
 *
 *
404 Not Found target

 *
 *
Use {@link #notFound(Object)}. It will be used as the target
 * when there's no match.
 *
 *

 * {@code
 * router.notFound(My404Handler.class);
 * }
 *

 *
 *
Create reverse route

 *
 *
Use {@code #uri}:
 *
 *

 * {@code
 * router.uri(HttpMethod.GET, IndexHandler.class);
 * // Returns "/articles"
 * }
 *

 *
 *
You can skip HTTP method if there's no confusion:
 *
 *

 * {@code
 * router.uri(CreateHandler.class);
 * // Also returns "/articles"
 * }
 *

 *
 *
You can specify params as map:
 *
 *

 * {@code
 * // Things in params will be converted to String
 * Map<Object, Object> params = new HashMap<Object, Object>();
 * params.put("id", 123);
 * router.uri(ShowHandler.class, params);
 * // Returns "/articles/123"
 * }
 *

 *
 *
Convenient way to specify params:
 *
 *

 * {@code
 * router.uri(ShowHandler.class, "id", 123);
 * // Returns "/articles/123"
 * }
 *

 *
 *
Allowed methods

 *
 *
If you want to implement
 * OPTIONS
 * or
 * CORS,
 * you can use {@link #allowedMethods(String)}.
 *
 *
For {@code OPTIONS *}, use {@link #allAllowedMethods()}.
 */
public class Router<T> {
    private final Map<HttpMethod, MethodlessRouter<T>> routers =
            new HashMap<HttpMethod, MethodlessRouter<T>>();

    private final MethodlessRouter<T> anyMethodRouter =
            new MethodlessRouter<T>();

    private T notFound;

    //--------------------------------------------------------------------------
    // Design decision:
    // We do not allow access to routers and anyMethodRouter, because we don't
    // want to expose MethodlessRouter, OrderlessRouter, and PathPattern.
    // Exposing those will complicate the use of this package.

    /**
     * Returns the fallback target for use when there's no match at
     * {@link #route(HttpMethod, String)}.
     */
    public T notFound() {
        return notFound;
    }

    /**
     * Returns the number of routes in this router.
     */
    public int size() {
        int ret = anyMethodRouter.size();

        for (MethodlessRouter<T> router : routers.values()) {
            ret += router.size();
        }

        return ret;
    }

    //--------------------------------------------------------------------------

    /**
     * Add route to the "first" section.
     *
     *
A path pattern can only point to one target. This method does nothing if the pattern
     * has already been added.
     */
    public Router<T> addRouteFirst(HttpMethod method, String pathPattern, T target) {
        getMethodlessRouter(method).addRouteFirst(pathPattern, target);
        return this;
    }

    /**
     * Add route to the "other" section.
     *
     *
A path pattern can only point to one target. This method does nothing if the pattern
     * has already been added.
     */
    public Router<T> addRoute(HttpMethod method, String pathPattern, T target) {
        getMethodlessRouter(method).addRoute(pathPattern, target);
        return this;
    }

    /**
     * Add route to the "last" section.
     *
     *
A path pattern can only point to one target. This method does nothing if the pattern
     * has already been added.
     */
    public Router<T> addRouteLast(HttpMethod method, String pathPattern, T target) {
        getMethodlessRouter(method).addRouteLast(pathPattern, target);
        return this;
    }

    /**
     * Sets the fallback target for use when there's no match at
     * {@link #route(HttpMethod, String)}.
     */
    public Router<T> notFound(T target) {
        this.notFound = target;
        return this;
    }

    private MethodlessRouter<T> getMethodlessRouter(HttpMethod method) {
        if (method == null) {
            return anyMethodRouter;
        }

        MethodlessRouter<T> r = routers.get(method);
        if (r == null) {
            r = new MethodlessRouter<T>();
            routers.put(method, r);
        }

        return r;
    }

    //--------------------------------------------------------------------------

    /**
     * Removes the route specified by the path pattern.
     */
    public void removePathPattern(String pathPattern) {
        for (MethodlessRouter<T> r : routers.values()) {
            r.removePathPattern(pathPattern);
        }
        anyMethodRouter.removePathPattern(pathPattern);
    }

    /**
     * Removes all routes leading to the target.
     */
    public void removeTarget(T target) {
        for (MethodlessRouter<T> r : routers.values()) {
            r.removeTarget(target);
        }
        anyMethodRouter.removeTarget(target);
    }

    //--------------------------------------------------------------------------

    /**
     * If there's no match, returns the result with {@link #notFound(Object) notFound}
     * as the target if it is set, otherwise returns {@code null}.
     */
    public RouteResult<T> route(HttpMethod method, String uri) {
        MethodlessRouter<T> router = routers.get(method);
        if (router == null) {
            router = anyMethodRouter;
        }

        QueryStringDecoder decoder = new QueryStringDecoder(uri);
        String[] tokens = decodePathTokens(uri);

        RouteResult<T> ret = router.route(uri, decoder.path(), tokens);
        if (ret != null) {
            return new RouteResult<T>(uri, decoder.path(), ret.pathParams(), decoder.parameters(), ret.target());
        }

        if (router != anyMethodRouter) {
            ret = anyMethodRouter.route(uri, decoder.path(), tokens);
            if (ret != null) {
                return new RouteResult<T>(uri, decoder.path(), ret.pathParams(), decoder.parameters(), ret.target());
            }
        }

        if (notFound != null) {
            return new RouteResult<T>(uri, decoder.path(), Collections.<String, String>emptyMap(), decoder.parameters(), notFound);
        }

        return null;
    }

    private String[] decodePathTokens(String uri) {
        // Need to split the original URI (instead of QueryStringDecoder#path) then decode the tokens (components),
        // otherwise /test1/123%2F456 will not match /test1/:p1

        int qPos = uri.indexOf("?");
        String encodedPath = (qPos >= 0) ? uri.substring(0, qPos) : uri;

        String[] encodedTokens = PathPattern.removeSlashesAtBothEnds(encodedPath).split("/");

        String[] decodedTokens = new String[encodedTokens.length];
        for (int i = 0; i < encodedTokens.length; i++) {
            String encodedToken = encodedTokens[i];
            decodedTokens[i] = QueryStringDecoder.decodeComponent(encodedToken);
        }

        return decodedTokens;
    }

    //--------------------------------------------------------------------------
    // For implementing OPTIONS and CORS.

    /**
     * Returns allowed methods for a specific URI.
     *

     * For {@code OPTIONS *}, use {@link #allAllowedMethods()} instead of this method.
     */
    public Set<HttpMethod> allowedMethods(String uri) {
        QueryStringDecoder decoder = new QueryStringDecoder(uri);
        String[] tokens = PathPattern.removeSlashesAtBothEnds(decoder.path()).split("/");

        if (anyMethodRouter.anyMatched(tokens)) {
            return allAllowedMethods();
        }

        Set<HttpMethod> ret = new HashSet<HttpMethod>(routers.size());
        for (Map.Entry<HttpMethod, MethodlessRouter<T>> entry : routers.entrySet()) {
            MethodlessRouter<T> router = entry.getValue();
            if (router.anyMatched(tokens)) {
                HttpMethod method = entry.getKey();
                ret.add(method);
            }
        }

        return ret;
    }

    /**
     * Returns all methods that this router handles. For {@code OPTIONS *}.
     */
    public Set<HttpMethod> allAllowedMethods() {
        if (anyMethodRouter.size() > 0) {
            Set<HttpMethod> ret = new HashSet<HttpMethod>(9);
            ret.add(HttpMethod.CONNECT);
            ret.add(HttpMethod.DELETE);
            ret.add(HttpMethod.GET);
            ret.add(HttpMethod.HEAD);
            ret.add(HttpMethod.OPTIONS);
            ret.add(HttpMethod.PATCH);
            ret.add(HttpMethod.POST);
            ret.add(HttpMethod.PUT);
            ret.add(HttpMethod.TRACE);
            return ret;
        } else {
            return new HashSet<HttpMethod>(routers.keySet());
        }
    }

    //--------------------------------------------------------------------------
    // Reverse routing.

    /**
     * Given a target and params, this method tries to do the reverse routing
     * and returns the URI.
     *
     *
Placeholders in the path pattern will be filled with the params.
     * The params can be a map of {@code placeholder name -> value}
     * or ordered values.
     *
     *
If a param doesn't have a corresponding placeholder, it will be put
     * to the query part of the result URI.
     *
     * @return {@code null} if there's no match
     */
    public String uri(HttpMethod method, T target, Object... params) {
        MethodlessRouter<T> router = (method == null) ? anyMethodRouter : routers.get(method);

        // Fallback to anyMethodRouter if no router is found for the method
        if (router == null) {
            router = anyMethodRouter;
        }

        String ret = router.uri(target, params);
        if (ret != null) {
            return ret;
        }

        // Fallback to anyMethodRouter if the router was not anyMethodRouter and no path is found
        return (router != anyMethodRouter) ? anyMethodRouter.uri(target, params) : null;
    }

    /**
     * Given a target and params, this method tries to do the reverse routing
     * and returns the URI.
     *
     *
Placeholders in the path pattern will be filled with the params.
     * The params can be a map of {@code placeholder name -> value}
     * or ordered values.
     *
     *
If a param doesn't have a corresponding placeholder, it will be put
     * to the query part of the result URI.
     *
     * @return {@code null} if there's no match
     */
    public String uri(T target, Object... params) {
        Collection<MethodlessRouter<T>> rs = routers.values();
        for (MethodlessRouter<T> r : rs) {
            String ret = r.uri(target, params);
            if (ret != null) {
                return ret;
            }
        }
        return anyMethodRouter.uri(target, params);
    }

    //--------------------------------------------------------------------------

    /**
     * Returns visualized routing rules.
     */
    @Override
    public String toString() {
        // Step 1/2: Dump routers and anyMethodRouter in order
        int numRoutes = size();
        List<String> methods = new ArrayList<String>(numRoutes);
        List<String> patterns = new ArrayList<String>(numRoutes);
        List<String> targets = new ArrayList<String>(numRoutes);

        // For router
        for (Entry<HttpMethod, MethodlessRouter<T>> e : routers.entrySet()) {
            HttpMethod method = e.getKey();
            MethodlessRouter<T> router = e.getValue();
            aggregateRoutes(method.toString(), router.first().routes(), methods, patterns, targets);
            aggregateRoutes(method.toString(), router.other().routes(), methods, patterns, targets);
            aggregateRoutes(method.toString(), router.last().routes(), methods, patterns, targets);
        }

        // For anyMethodRouter
        aggregateRoutes("*", anyMethodRouter.first().routes(), methods, patterns, targets);
        aggregateRoutes("*", anyMethodRouter.other().routes(), methods, patterns, targets);
        aggregateRoutes("*", anyMethodRouter.last().routes(), methods, patterns, targets);

        // For notFound
        if (notFound != null) {
            methods.add("*");
            patterns.add("*");
            targets.add(targetToString(notFound));
        }

        // Step 2/2: Format the List into aligned columns: <method> <patterns> <target>
        int maxLengthMethod = maxLength(methods);
        int maxLengthPattern = maxLength(patterns);
        String format = "%-" + maxLengthMethod + "s  %-" + maxLengthPattern + "s  %s\n";
        int initialCapacity = (maxLengthMethod + 1 + maxLengthPattern + 1 + 20) * methods.size();
        StringBuilder b = new StringBuilder(initialCapacity);
        for (int i = 0; i < methods.size(); i++) {
            String method = methods.get(i);
            String pattern = patterns.get(i);
            String target = targets.get(i);
            b.append(String.format(format, method, pattern, target));
        }
        return b.toString();
    }

    /**
     * Helper for toString.
     */
    private static <T> void aggregateRoutes(
            String method, Map<PathPattern, T> routes,
            List<String> accMethods, List<String> accPatterns, List<String> accTargets) {
        for (Map.Entry<PathPattern, T> entry : routes.entrySet()) {
            accMethods.add(method);
            accPatterns.add("/" + entry.getKey().pattern());
            accTargets.add(targetToString(entry.getValue()));
        }
    }

    /**
     * Helper for toString.
     */
    private static int maxLength(List<String> coll) {
        int max = 0;
        for (String e : coll) {
            int length = e.length();
            if (length > max) {
                max = length;
            }
        }
        return max;
    }

    /**
     * Helper for toString.
     *
     *
For example, returns
     * "io.netty.example.http.router.HttpRouterServerHandler" instead of
     * "class io.netty.example.http.router.HttpRouterServerHandler"
     */
    private static String targetToString(Object target) {
        if (target instanceof Class) {
            return ((Class<?>) target).getName();
        } else {
            return target.toString();
        }
    }

    //--------------------------------------------------------------------------

    public Router<T> CONNECT(String path, T target) {
        return addRoute(HttpMethod.CONNECT, path, target);
    }

    public Router<T> DELETE(String path, T target) {
        return addRoute(HttpMethod.DELETE, path, target);
    }

    public Router<T> GET(String path, T target) {
        return addRoute(HttpMethod.GET, path, target);
    }

    public Router<T> HEAD(String path, T target) {
        return addRoute(HttpMethod.HEAD, path, target);
    }

    public Router<T> OPTIONS(String path, T target) {
        return addRoute(HttpMethod.OPTIONS, path, target);
    }

    public Router<T> PATCH(String path, T target) {
        return addRoute(HttpMethod.PATCH, path, target);
    }

    public Router<T> POST(String path, T target) {
        return addRoute(HttpMethod.POST, path, target);
    }

    public Router<T> PUT(String path, T target) {
        return addRoute(HttpMethod.PUT, path, target);
    }

    public Router<T> TRACE(String path, T target) {
        return addRoute(HttpMethod.TRACE, path, target);
    }

    public Router<T> ANY(String path, T target) {
        return addRoute(null, path, target);
    }

    //--------------------------------------------------------------------------

    public Router<T> CONNECT_FIRST(String path, T target) {
        return addRouteFirst(HttpMethod.CONNECT, path, target);
    }

    public Router<T> DELETE_FIRST(String path, T target) {
        return addRouteFirst(HttpMethod.DELETE, path, target);
    }

    public Router<T> GET_FIRST(String path, T target) {
        return addRouteFirst(HttpMethod.GET, path, target);
    }

    public Router<T> HEAD_FIRST(String path, T target) {
        return addRouteFirst(HttpMethod.HEAD, path, target);
    }

    public Router<T> OPTIONS_FIRST(String path, T target) {
        return addRouteFirst(HttpMethod.OPTIONS, path, target);
    }

    public Router<T> PATCH_FIRST(String path, T target) {
        return addRouteFirst(HttpMethod.PATCH, path, target);
    }

    public Router<T> POST_FIRST(String path, T target) {
        return addRouteFirst(HttpMethod.POST, path, target);
    }

    public Router<T> PUT_FIRST(String path, T target) {
        return addRouteFirst(HttpMethod.PUT, path, target);
    }

    public Router<T> TRACE_FIRST(String path, T target) {
        return addRouteFirst(HttpMethod.TRACE, path, target);
    }

    public Router<T> ANY_FIRST(String path, T target) {
        return addRouteFirst(null, path, target);
    }

    //--------------------------------------------------------------------------

    public Router<T> CONNECT_LAST(String path, T target) {
        return addRouteLast(HttpMethod.CONNECT, path, target);
    }

    public Router<T> DELETE_LAST(String path, T target) {
        return addRouteLast(HttpMethod.DELETE, path, target);
    }

    public Router<T> GET_LAST(String path, T target) {
        return addRouteLast(HttpMethod.GET, path, target);
    }

    public Router<T> HEAD_LAST(String path, T target) {
        return addRouteLast(HttpMethod.HEAD, path, target);
    }

    public Router<T> OPTIONS_LAST(String path, T target) {
        return addRouteLast(HttpMethod.OPTIONS, path, target);
    }

    public Router<T> PATCH_LAST(String path, T target) {
        return addRouteLast(HttpMethod.PATCH, path, target);
    }

    public Router<T> POST_LAST(String path, T target) {
        return addRouteLast(HttpMethod.POST, path, target);
    }

    public Router<T> PUT_LAST(String path, T target) {
        return addRouteLast(HttpMethod.PUT, path, target);
    }

    public Router<T> TRACE_LAST(String path, T target) {
        return addRouteLast(HttpMethod.TRACE, path, target);
    }

    public Router<T> ANY_LAST(String path, T target) {
        return addRouteLast(null, path, target);
    }
}


/*
 * Copyright 2015 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package io.netty.handler.codec.http.router;

import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Router that doesn't contain information about HTTP request methods and route
 * matching orders.
 */
final class OrderlessRouter<T> {
    private static final InternalLogger log = InternalLoggerFactory.getInstance(OrderlessRouter.class);

    // A path pattern can only point to one target
    private final Map<PathPattern, T> routes = new HashMap<PathPattern, T>();

    // Reverse index to create reverse routes fast (a target can have multiple path patterns)
    private final Map<T, Set<PathPattern>> reverseRoutes = new HashMap<T, Set<PathPattern>>();

    //--------------------------------------------------------------------------

    /**
     * Returns all routes in this router, an unmodifiable map of {@code PathPattern -> Target}.
     */
    public Map<PathPattern, T> routes() {
        return Collections.unmodifiableMap(routes);
    }

    /**
     * This method does nothing if the path pattern has already been added.
     * A path pattern can only point to one target.
     */
    public OrderlessRouter<T> addRoute(String pathPattern, T target) {
        PathPattern p = new PathPattern(pathPattern);
        if (routes.containsKey(p)) {
            return this;
        }

        routes.put(p, target);
        addReverseRoute(target, p);
        return this;
    }

    private void addReverseRoute(T target, PathPattern pathPattern) {
        Set<PathPattern> patterns = reverseRoutes.get(target);
        if (patterns == null) {
            patterns = new HashSet<PathPattern>();
            patterns.add(pathPattern);
            reverseRoutes.put(target, patterns);
        } else {
            patterns.add(pathPattern);
        }
    }

    //--------------------------------------------------------------------------

    /**
     * Removes the route specified by the path pattern.
     */
    public void removePathPattern(String pathPattern) {
        PathPattern p = new PathPattern(pathPattern);
        T target = routes.remove(p);
        if (target == null) {
            return;
        }

        Set<PathPattern> paths = reverseRoutes.remove(target);
        paths.remove(p);
    }

    /**
     * Removes all routes leading to the target.
     */
    public void removeTarget(T target) {
        Set<PathPattern> patterns = reverseRoutes.remove(ObjectUtil.checkNotNull(target, "target"));
        if (patterns == null) {
            return;
        }

        // A pattern can only point to one target.
        // A target can have multiple patterns.
        // Remove all patterns leading to this target.
        for (PathPattern pattern : patterns) {
            routes.remove(pattern);
        }
    }

    //--------------------------------------------------------------------------

    /**
     * @return {@code null} if no match
     */
    public RouteResult<T> route(String uri, String decodedPath, String[] pathTokens) {
        // Optimize: reuse requestPathTokens and pathParams in the loop
        Map<String, String> pathParams = new HashMap<String, String>();
        for (Map.Entry<PathPattern, T> entry : routes.entrySet()) {
            PathPattern pattern = entry.getKey();
            if (pattern.match(pathTokens, pathParams)) {
                T target = entry.getValue();
                return new RouteResult<T>(uri, decodedPath, pathParams, Collections.<String, List<String>>emptyMap(), target);
            }

            // Reset for the next try
            pathParams.clear();
        }

        return null;
    }

    /**
     * Checks if there's any matching route.
     */
    public boolean anyMatched(String[] requestPathTokens) {
        Map<String, String> pathParams = new HashMap<String, String>();
        for (PathPattern pattern : routes.keySet()) {
            if (pattern.match(requestPathTokens, pathParams)) {
                return true;
            }

            // Reset for the next loop
            pathParams.clear();
        }

        return false;
    }

    //--------------------------------------------------------------------------

    /**
     * Given a target and params, this method tries to do the reverse routing
     * and returns the URI.
     *
     *
Placeholders in the path pattern will be filled with the params.
     * The params can be a map of {@code placeholder name -> value}
     * or ordered values.
     *
     *
If a param doesn't have a corresponding placeholder, it will be put
     * to the query part of the result URI.
     *
     * @return {@code null} if there's no match
     */
    @SuppressWarnings("unchecked")
    public String uri(T target, Object... params) {
        if (params.length == 0) {
            return uri(target, Collections.emptyMap());
        }

        if (params.length == 1 && params[0] instanceof Map<?, ?>) {
            return pathMap(target, (Map<Object, Object>) params[0]);
        }

        if (params.length % 2 == 1) {
            throw new IllegalArgumentException("Missing value for param: " + params[params.length - 1]);
        }

        Map<Object, Object> map = new HashMap<Object, Object>(params.length / 2);
        for (int i = 0; i < params.length; i += 2) {
            String key = params[i].toString();
            String value = params[i + 1].toString();
            map.put(key, value);
        }
        return pathMap(target, map);
    }

    /**
     * @return {@code null} if there's no match, or the params can't be UTF-8 encoded
     */
    private String pathMap(T target, Map<Object, Object> params) {
        Set<PathPattern> patterns = reverseRoutes.get(target);
        if (patterns == null) {
            return null;
        }

        try {
            // The best one is the one with minimum number of params in the query
            String bestCandidate = null;
            int minQueryParams = Integer.MAX_VALUE;

            boolean matched = true;
            Set<String> usedKeys = new HashSet<String>();

            for (PathPattern pattern : patterns) {
                matched = true;
                usedKeys.clear();

                // "+ 16": Just in case the part befor that is 0
                int initialCapacity = pattern.pattern().length() + 20 * params.size() + 16;
                StringBuilder b = new StringBuilder(initialCapacity);

                for (String token : pattern.tokens()) {
                    b.append('/');

                    if (token.length() > 0 && token.charAt(0) == ':') {
                        String key = token.substring(1);
                        Object value = params.get(key);
                        if (value == null) {
                            matched = false;
                            break;
                        }

                        usedKeys.add(key);
                        b.append(value.toString());
                    } else {
                        b.append(token);
                    }
                }

                if (matched) {
                    int numQueryParams = params.size() - usedKeys.size();
                    if (numQueryParams < minQueryParams) {
                        if (numQueryParams > 0) {
                            boolean firstQueryParam = true;

                            for (Map.Entry<Object, Object> entry : params.entrySet()) {
                                String key = entry.getKey().toString();
                                if (!usedKeys.contains(key)) {
                                    if (firstQueryParam) {
                                        b.append('?');
                                        firstQueryParam = false;
                                    } else {
                                        b.append('&');
                                    }

                                    String value = entry.getValue().toString();

                                    // May throw UnsupportedEncodingException
                                    b.append(URLEncoder.encode(key, "UTF-8"));

                                    b.append('=');

                                    // May throw UnsupportedEncodingException
                                    b.append(URLEncoder.encode(value, "UTF-8"));
                                }
                            }
                        }

                        bestCandidate = b.toString();
                        minQueryParams = numQueryParams;
                    }
                }
            }

            return bestCandidate;
        } catch (UnsupportedEncodingException e) {
            log.warn("Params can't be UTF-8 encoded: " + params);
            return null;
        }
    }
}


/*
 * Copyright 2015 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package io.netty.handler.codec.http.router;

/**
 * Router that contains information about route matching orders, but doesn't
 * contain information about HTTP request methods.
 *
 *
Routes are devided into 3 sections: "first", "last", and "other".
 * Routes in "first" are matched first, then in "other", then in "last".
 */
final class MethodlessRouter<T> {
    private final OrderlessRouter<T> first = new OrderlessRouter<T>();
    private final OrderlessRouter<T> other = new OrderlessRouter<T>();
    private final OrderlessRouter<T> last = new OrderlessRouter<T>();

    //--------------------------------------------------------------------------

    /**
     * Returns the "first" router; routes in this router will be matched first.
     */
    public OrderlessRouter<T> first() {
        return first;
    }

    /**
     * Returns the "other" router; routes in this router will be matched after
     * those in the "first" router, but before those in the "last" router.
     */
    public OrderlessRouter<T> other() {
        return other;
    }

    /**
     * Returns the "last" router; routes in this router will be matched last.
     */
    public OrderlessRouter<T> last() {
        return last;
    }

    /**
     * Returns the number of routes in this router.
     */
    public int size() {
        return first.routes().size() + other.routes().size() + last.routes().size();
    }

    //--------------------------------------------------------------------------

    /**
     * Adds route to the "first" section.
     *
     *
A path pattern can only point to one target. This method does nothing if the pattern
     * has already been added.
     */
    public MethodlessRouter<T> addRouteFirst(String pathPattern, T target) {
        first.addRoute(pathPattern, target);
        return this;
    }

    /**
     * Adds route to the "other" section.
     *
     *
A path pattern can only point to one target. This method does nothing if the pattern
     * has already been added.
     */
    public MethodlessRouter<T> addRoute(String pathPattern, T target) {
        other.addRoute(pathPattern, target);
        return this;
    }

    /**
     * Adds route to the "last" section.
     *
     *
A path pattern can only point to one target. This method does nothing if the pattern
     * has already been added.
     */
    public MethodlessRouter<T> addRouteLast(String pathPattern, T target) {
        last.addRoute(pathPattern, target);
        return this;
    }

    //--------------------------------------------------------------------------

    /**
     * Removes the route specified by the path pattern.
     */
    public void removePathPattern(String pathPattern) {
        first.removePathPattern(pathPattern);
        other.removePathPattern(pathPattern);
        last.removePathPattern(pathPattern);
    }

    /**
     * Removes all routes leading to the target.
     */
    public void removeTarget(T target) {
        first.removeTarget(target);
        other.removeTarget(target);
        last.removeTarget(target);
    }

    //--------------------------------------------------------------------------

    /**
     * @return {@code null} if no match
     */
    public RouteResult<T> route(String uri, String decodedPath, String[] pathTokens) {
        RouteResult<T> ret = first.route(uri, decodedPath, pathTokens);
        if (ret != null) {
            return ret;
        }

        ret = other.route(uri, decodedPath, pathTokens);
        if (ret != null) {
            return ret;
        }

        ret = last.route(uri, decodedPath, pathTokens);
        if (ret != null) {
            return ret;
        }

        return null;
    }

    /**
     * Checks if there's any matching route.
     */
    public boolean anyMatched(String[] requestPathTokens) {
        return first.anyMatched(requestPathTokens) ||
                other.anyMatched(requestPathTokens) ||
                last.anyMatched(requestPathTokens);
    }

    /**
     * Given a target and params, this method tries to do the reverse routing
     * and returns the URI.
     *
     *
Placeholders in the path pattern will be filled with the params.
     * The params can be a map of {@code placeholder name -> value}
     * or ordered values.
     *
     *
If a param doesn't have a corresponding placeholder, it will be put
     * to the query part of the result URI.
     *
     * @return {@code null} if there's no match
     */
    public String uri(T target, Object... params) {
        String ret = first.uri(target, params);
        if (ret != null) {
            return ret;
        }

        ret = other.uri(target, params);
        if (ret != null) {
            return ret;
        }

        return last.uri(target, params);
    }
}


/*
 * Copyright 2015 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package io.netty.handler.codec.http;

import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

/**
 * This utility handler should be put at the last position of the inbound pipeline to
 * catch all exceptions caused by bad client (closed connection, malformed request etc.)
 * and server processing, then close the connection.
 *
 * By default exceptions are logged to Netty internal logger. You may need to override
 * {@link #onUnknownMessage(Object)}, {@link #onBadClient(Throwable)}, and
 * {@link #onBadServer(Throwable)} to log to more suitable places.
 */
@Sharable
public class BadClientSilencer extends SimpleChannelInboundHandler<Object> {
    private static final InternalLogger log = InternalLoggerFactory.getInstance(BadClientSilencer.class);

    /** Logs to Netty internal logger. Override this method to log to other places if you want. */
    protected void onUnknownMessage(Object msg) {
        log.warn("Unknown msg: " + msg);
    }

    /** Logs to Netty internal logger. Override this method to log to other places if you want. */
    protected void onBadClient(Throwable e) {
        log.warn("Caught exception (maybe client is bad)", e);
    }

    /** Logs to Netty internal logger. Override this method to log to other places if you want. */
    protected void onBadServer(Throwable e) {
        log.warn("Caught exception (maybe server is bad)", e);
    }

    //----------------------------------------------------------------------------

    @Override
    public void channelRead0(ChannelHandlerContext ctx, Object msg) {
        // This handler is the last inbound handler.
        // This means msg has not been handled by any previous handler.
        ctx.close();

        if (msg != LastHttpContent.EMPTY_LAST_CONTENT) {
            onUnknownMessage(msg);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) {
        ctx.close();

        // To clarify where exceptions are from, imports are not used
        if (e instanceof java.io.IOException                            ||  // Connection reset by peer, Broken pipe
            e instanceof java.nio.channels.ClosedChannelException       ||
            e instanceof io.netty.handler.codec.DecoderException        ||
            e instanceof io.netty.handler.codec.CorruptedFrameException ||  // Bad WebSocket frame
            e instanceof java.lang.IllegalArgumentException             ||  // Use https://... to connect to HTTP server
            e instanceof javax.net.ssl.SSLException                     ||  // Use http://... to connect to HTTPS server
            e instanceof io.netty.handler.ssl.NotSslRecordException) {
            onBadClient(e);  // Maybe client is bad
        } else {
            onBadServer(e);  // Maybe server is bad
        }
    }
}


/**
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License.  You may obtain a
 * copy of the License at the following location:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package edu.wisc.my.portlets.bookmarks.dao.hibernate;

import java.util.Date;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Restrictions;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import edu.wisc.my.portlets.bookmarks.dao.BookmarkStore;
import edu.wisc.my.portlets.bookmarks.domain.BookmarkSet;

/**
 * Persists and retrieves BookmarkSet objects via Hibernate.
 *
 * @author Eric Dalquist eric.dalquist@doit.wisc.edu
 * @version $Revision: 12173 $
 */
public class HibernateBookmarkStore extends HibernateDaoSupport implements BookmarkStore {
    private static final String PARAM_OWNER = "owner";
    private static final String PARAM_NAME = "name";

    /**
     * @see edu.wisc.my.portlets.bookmarks.dao.BookmarkStore#getBookmarkSet(java.lang.String, java.lang.String)
     */
    public BookmarkSet getBookmarkSet(String owner, String name) {
        try {
            final Session session = this.getSession(false);

            final Criteria c = session.createCriteria(BookmarkSet.class);
            c.add(Restrictions.and(this.smartEqual(PARAM_NAME, name),
                                   this.smartEqual(PARAM_OWNER, owner)));
            c.setCacheable(true);

            final BookmarkSet bs = (BookmarkSet)c.uniqueResult();

            return bs;
        }
        catch (HibernateException ex) {
            throw convertHibernateAccessException(ex);
        }
    }

    /**
     * @see edu.wisc.my.portlets.bookmarks.dao.BookmarkStore#storeBookmarkSet(edu.wisc.my.portlets.bookmarks.domain.BookmarkSet)
     */
    public void storeBookmarkSet(BookmarkSet bookmarkSet) {
        try {
            final Session session = this.getSession(false);

            //If the BookmarkSet is new it must be saved first
            if (bookmarkSet.getId() == -1) {
                //Ensure there won't be duplicate entries with this owner & name
                this.removeBookmarkSet(bookmarkSet.getOwner(), bookmarkSet.getName());
                session.save(bookmarkSet);
            }

            session.update(bookmarkSet);
            session.flush();
        }
        catch (HibernateException ex) {
            throw convertHibernateAccessException(ex);
        }
    }

    /**
     * @see edu.wisc.my.portlets.bookmarks.dao.BookmarkStore#removeBookmarkSet(java.lang.String, java.lang.String)
     */
    public void removeBookmarkSet(String owner, String name) {
        try {
            final Session session = this.getSession(false);

            final BookmarkSet set = this.getBookmarkSet(owner, name);
            if (set != null) {
                session.delete(set);
            }

            session.flush();
        }
        catch (HibernateException ex) {
            throw convertHibernateAccessException(ex);
        }
    }

    /**
     * @see edu.wisc.my.portlets.bookmarks.dao.BookmarkStore#createBookmarkSet(java.lang.String, java.lang.String)
     */
    public BookmarkSet createBookmarkSet(String owner, String name) {
        final BookmarkSet bookmarkSet = new BookmarkSet();
        bookmarkSet.setOwner(owner);
        bookmarkSet.setName(name);
        bookmarkSet.setCreated(new Date());
        bookmarkSet.setModified(bookmarkSet.getCreated());

        this.storeBookmarkSet(bookmarkSet);

        return bookmarkSet;
    }

    /**
     * There are two different Criterion for if a property is null or checking equality. This is a
     * convience method to return the one based on if value is null or not.
     */
    private Criterion smartEqual(String property, Object value) {
        if (value == null) {
            return Restrictions.isNull(property);
        }
        else {
            return Restrictions.eq(property, value);
        }
    }
}


/**
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License.  You may obtain a
 * copy of the License at the following location:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package edu.wisc.my.portlets.bookmarks.dao.file;

import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.Date;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.DataAccessResourceFailureException;

import edu.wisc.my.portlets.bookmarks.dao.BookmarkStore;
import edu.wisc.my.portlets.bookmarks.domain.BookmarkSet;

/**
 * @author Eric Dalquist eric.dalquist@doit.wisc.edu
 * @version $Revision: 12150 $
 */
public class FileSystemBookmarkStore implements BookmarkStore {
    protected final Log logger = LogFactory.getLog(this.getClass());

    private String baseStorePath = null;

    /**
     * @return Returns the baseStorePath.
     */
    public String getBaseStorePath() {
        return this.baseStorePath;
    }
    /**
     * @param baseStorePath The baseStorePath to set.
     */
    public void setBaseStorePath(String baseStorePath) {
        this.baseStorePath = baseStorePath;
    }

    /**
     * @see edu.wisc.my.portlets.bookmarks.dao.BookmarkStore#getBookmarkSet(java.lang.String, java.lang.String)
     */
    public BookmarkSet getBookmarkSet(String owner, String name) {
        final File storeFile = this.getStoreFile(owner, name);

        //Ok if the file doesn't exist, the user hasn't stored one yet.
        if (!storeFile.exists()) {
            return null;
        }

        try {
            final FileInputStream fis = new FileInputStream(storeFile);
            final BufferedInputStream bis = new BufferedInputStream(fis);
            final XMLDecoder d = new XMLDecoder(bis);

            try {
                final BookmarkSet bs = (BookmarkSet)d.readObject();
                return bs;
            }
            finally {
                d.close();
            }
        }
        catch (FileNotFoundException fnfe) {
            final String errorMsg = "Error reading BookmarkSet for owner='" + owner + "', name='" + name + "' from file='" + storeFile + "'";
            logger.error(errorMsg, fnfe);
            throw new DataAccessResourceFailureException(errorMsg);
        }
    }

    /**
     * @see edu.wisc.my.portlets.bookmarks.dao.BookmarkStore#storeBookmarkSet(edu.wisc.my.portlets.bookmarks.domain.BookmarkSet)
     */
    public void storeBookmarkSet(BookmarkSet bookmarkSet) {
        if (bookmarkSet == null) {
            throw new IllegalArgumentException("AddressBook may not be null");
        }

        final File storeFile = this.getStoreFile(bookmarkSet.getOwner(), bookmarkSet.getName());

        try {
            final FileOutputStream fos = new FileOutputStream(storeFile);
            final BufferedOutputStream bos = new BufferedOutputStream(fos);
            final XMLEncoder e = new XMLEncoder(bos);
            try {
                e.writeObject(bookmarkSet);
            }
            finally {
                e.close();
            }
        }
        catch (FileNotFoundException fnfe) {
            final String errorMsg = "Error storing BookmarkSet='" + bookmarkSet + "' to file='" + storeFile + "'";
            logger.error(errorMsg, fnfe);
            throw new DataAccessResourceFailureException(errorMsg, fnfe);
        }
    }

    /**
     * @see edu.wisc.my.portlets.bookmarks.dao.BookmarkStore#removeBookmarkSet(java.lang.String, java.lang.String)
     */
    public void removeBookmarkSet(String owner, String name) {
        final File storeFile = this.getStoreFile(owner, name);
        storeFile.delete();
    }

    /**
     * @see edu.wisc.my.portlets.bookmarks.dao.BookmarkStore#createBookmarkSet(java.lang.String, java.lang.String)
     */
    public BookmarkSet createBookmarkSet(String owner, String name) {
        final BookmarkSet bookmarkSet = new BookmarkSet();
        bookmarkSet.setOwner(owner);
        bookmarkSet.setName(name);
        bookmarkSet.setCreated(new Date());
        bookmarkSet.setModified(bookmarkSet.getCreated());

        this.storeBookmarkSet(bookmarkSet);

        return bookmarkSet;
    }

    /**
     * Generates the file name String for an owner and book name.
     *
     * @param owner The owner of the bookmark set.
     * @param name The name of the bookmark set.
     * @return The file name for the owner and name.
     */
    protected String getStoreFileName(String owner, String name) {
        final StringBuilder fileNameBuff = new StringBuilder();

        fileNameBuff.append(owner != null ? "_" + owner : "null");
        fileNameBuff.append("_");
        fileNameBuff.append(name != null ? "_" + name : "null");
        fileNameBuff.append(".bms.xml");

        return fileNameBuff.toString();
    }

    /**
     * Generates the {@link File} object to use for storing, retrieving
     * and deleting the bookmark set.
     *
     * @param owner The owner of the bookmark set.
     * @param name The name of the bookmark set.
     * @return The File for the owner and name.
     */
    protected File getStoreFile(String owner, String name) {
        final String fileStoreName = this.getStoreFileName(owner, name);
        final File basePath = this.getStoreDirectory();
        final File storeFile = new File(basePath, fileStoreName);
        return storeFile;
    }

    /**
     * @return The directory to store AddressBooks in.
     */
    protected File getStoreDirectory() {
        return (this.baseStorePath == null ? new File(".") : new File(this.baseStorePath));
    }
}


/**
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License.  You may obtain a
 * copy of the License at the following location:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package edu.wisc.my.portlets.bookmarks.web.support;

import javax.portlet.PortletRequest;

import edu.wisc.my.portlets.bookmarks.dao.BookmarkStore;
import edu.wisc.my.portlets.bookmarks.domain.BookmarkSet;

/**
 * @author Eric Dalquist eric.dalquist@doit.wisc.edu
 * @version $Revision: 12161 $
 */
public class BookmarkSetRequestResolver {
    protected BookmarkStore bookmarkStore;
    protected OwnerResolver ownerResolver;
    protected NameResolver nameResolver;

    /**
     * @return Returns the bookmarkStore.
     */
    public BookmarkStore getBookmarkStore() {
        return this.bookmarkStore;
    }

    /**
     * @param bookmarkStore The bookmarkStore to set.
     */
    public void setBookmarkStore(BookmarkStore bookmarkStore) {
        this.bookmarkStore = bookmarkStore;
    }

    /**
     * @return Returns the nameResolver.
     */
    public NameResolver getNameResolver() {
        return this.nameResolver;
    }

    /**
     * @param nameResolver The nameResolver to set.
     */
    public void setNameResolver(NameResolver nameResolver) {
        this.nameResolver = nameResolver;
    }

    /**
     * @return Returns the ownerResolver.
     */
    public OwnerResolver getOwnerResolver() {
        return this.ownerResolver;
    }

    /**
     * @param ownerResolver The ownerResolver to set.
     */
    public void setOwnerResolver(OwnerResolver ownerResolver) {
        this.ownerResolver = ownerResolver;
    }

    /**
     * Calls getBookmarkSet(request, true);
     *
     * @see #getBookmarkSet(PortletRequest, boolean)
     */
    public BookmarkSet getBookmarkSet(PortletRequest request) {
        return this.getBookmarkSet(request, true);
    }

    /**
     * Gets a BookmarkSet for the request using the injected {@link OwnerResolver}
     * and {@link NameResolver}.
     *

     *

     * If create is false and no BookmarkSet exists for the name and owner null is returned.
     *

     *

     * If create is true and no BookmarkSet exists for the name and owner a new BookmarkSet is created.
     *
     * @param request The request to resolve the name and owner from.
     * @param create If a BookmarkSet should be created for the name and owner if one does not exist
     * @return The BookmarkSet for the name and owner from the request, null if it does not exists and create is false. If create is true this will never return null.
     */
    public BookmarkSet getBookmarkSet(PortletRequest request, boolean create) {
        final String owner = this.ownerResolver.getOwner(request);
        final String name = this.nameResolver.getBookmarkSetName(request);
        BookmarkSet bookmarkSet = this.bookmarkStore.getBookmarkSet(owner, name);

        if (bookmarkSet == null && create) {
            bookmarkSet = this.bookmarkStore.createBookmarkSet(owner, name);

            if (bookmarkSet == null) {
                throw new IllegalStateException("Required BookmarkSet is null even after createBookmarkSet was called.");
            }
        }

        return bookmarkSet;
    }
}


/**
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License.  You may obtain a
 * copy of the License at the following location:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package edu.wisc.my.portlets.bookmarks.domain;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;

import edu.wisc.my.portlets.bookmarks.domain.compare.DefaultBookmarksComparator;
import edu.wisc.my.portlets.bookmarks.domain.support.IntegerSetThreadLocal;

/**
 * A Folder can contain other entries in a List. This is a basic bean and no defensive copying
 * is done. Changes made to List returned by {@link #getChildren()} will be reflected to other
 * code using these APIs.
 *
 * @author Eric Dalquist eric.dalquist@doit.wisc.edu
 * @version $Revision: 12155 $
 */
public class Folder extends Entry implements CollapsibleEntry {
    private static final long serialVersionUID = 1L;

    private static IntegerSetThreadLocal equalsVisitedFolder = new IntegerSetThreadLocal();
    private static IntegerSetThreadLocal hashCodeVisitedFolder = new IntegerSetThreadLocal();
    private static IntegerSetThreadLocal toStringVisitedFolder = new IntegerSetThreadLocal();

    private Map<Long, Entry> children;
    private transient Comparator<Entry> childComparator = DefaultBookmarksComparator.DEFAULT_BOOKMARKS_COMPARATOR;
    private boolean minimized = false;

    /**
     * @return Returns the children, will never return null.
     */
    public synchronized Map<Long, Entry> getChildren() {
        if (this.children == null) {
            this.children = new HashMap<Long, Entry>();
        }

        return this.children;
    }

    /**
     * @param children The children to set.
     */
    public void setChildren(Map<Long, Entry> children) {
        this.children = children;
    }

    /**
     * @return Returns the minimized.
     */
    public boolean isMinimized() {
        return this.minimized;
    }

    /**
     * @param minimized The minimized to set.
     */
    public void setMinimized(boolean minimized) {
        this.minimized = minimized;
    }

    /**
     * Returns an immutable sorted view of the values of the children Map. The sorting is done
     * using the current childComparator. Warning, this is has a time cost of 2n log(n)
     * on every call.
     *
     * @return An immutable sorted view of the folder's children.
     */
    public List<Entry> getSortedChildren() {
        if (this.children == null) {
            return null;
        }
        else {
            final Collection<Entry> childCollection = this.children.values();
            final List<Entry> childList = new ArrayList<Entry>(childCollection);
            Collections.sort(childList, this.childComparator);
            return Collections.unmodifiableList(childList);
        }
    }

    /**
     * @return Returns the childComparator.
     */
    public Comparator<Entry> getChildComparator() {
        return this.childComparator;
    }

    /**
     * @param childComparator The childComparator to set, calls setChildComparator on all children of type Folder.
     */
    public void setChildComparator(Comparator<Entry> childComparator) {
        if (childComparator == null) {
            throw new IllegalArgumentException("childComparator cannot be null");
        }

        this.childComparator = childComparator;

        if (this.children != null) {
            //TODO loop detection?
            for (Map.Entry<Long, Entry> entry : this.children.entrySet()) {
                final Entry child = entry.getValue();

                if (child instanceof Folder) {
                    ((Folder)child).setChildComparator(childComparator);
                }
            }
        }
    }

    /**
     * @see java.lang.Object#equals(Object)
     */
    public boolean equals(final Object object) {
        final Set<Integer> visited = equalsVisitedFolder.getSet();
        final int identityHash = System.identityHashCode(this);
        try {
            if (!visited.add(identityHash)) {
                visited.clear();
                throw new IllegalStateException("A loop exists in the Folder tree.");
            }

            if (object == this) {
                return true;
            }
            if (!(object instanceof Folder)) {
                return false;
            }
            Folder rhs = (Folder)object;
            return new EqualsBuilder().appendSuper(super.equals(object))
                    .append(this.children, rhs.children)
                    .append(this.minimized, rhs.minimized)
                    .isEquals();
        }
        finally {
            visited.remove(identityHash);
        }
    }

    /**
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
        final Set<Integer> visited = hashCodeVisitedFolder.getSet();
        final int identityHash = System.identityHashCode(this);
        try {
            if (!visited.add(identityHash)) {
                visited.clear();
                throw new IllegalStateException("A loop exists in the Folder tree.");
            }

            return new HashCodeBuilder(-409984457, 961354191).appendSuper(super.hashCode())
                    .append(this.children)
                    .append(this.minimized)
                    .toHashCode();
        }
        finally {
            visited.remove(identityHash);
        }
    }

    /**
     * @see java.lang.Object#toString()
     */
    public String toString() {
        final Set<Integer> visited = toStringVisitedFolder.getSet();
        final int identityHash = System.identityHashCode(this);
        try {
            if (!visited.add(identityHash)) {
                visited.clear();
                throw new IllegalStateException("A loop exists in the Folder tree.");
            }

            return new ToStringBuilder(this).appendSuper(super.toString())
                    .append("children", this.children)
                    .append("minimized", this.minimized)
                    .toString();
        }
        finally {
            visited.remove(identityHash);
        }
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();

        if (this.childComparator instanceof Serializable) {
            out.writeObject(this.childComparator);
        }
        else {
            out.writeObject(this.childComparator.getClass());
        }
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();

        final Object comparatorInfo = in.readObject();
        if (comparatorInfo instanceof Comparator) {
            this.childComparator = this.castComparator(comparatorInfo);
        }
        else if (comparatorInfo instanceof Class && Comparator.class.isAssignableFrom((Class)comparatorInfo)) {
            try {
                final Object instance = ((Class)comparatorInfo).newInstance();
                this.childComparator = this.castComparator(instance);
            }
            catch (InstantiationException e) {
                this.childComparator = DefaultBookmarksComparator.DEFAULT_BOOKMARKS_COMPARATOR;
            }
            catch (IllegalAccessException e) {
                this.childComparator = DefaultBookmarksComparator.DEFAULT_BOOKMARKS_COMPARATOR;
            }
        }
        else {
            this.childComparator = DefaultBookmarksComparator.DEFAULT_BOOKMARKS_COMPARATOR;
        }
    }

    /**
     * Casts an Object to a Comparator<Entry>. Allows the warning suppression to be as localized
     * as possible.
     */
    @SuppressWarnings("unchecked")
    private Comparator<Entry> castComparator(Object obj) {
        return (Comparator<Entry>)obj;
    }
}


/**
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License.  You may obtain a
 * copy of the License at the following location:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package edu.wisc.my.portlets.bookmarks.domain.compare;

import java.io.Serializable;
import java.util.Comparator;

import org.apache.commons.lang.builder.CompareToBuilder;

import edu.wisc.my.portlets.bookmarks.domain.Bookmark;
import edu.wisc.my.portlets.bookmarks.domain.Entry;
import edu.wisc.my.portlets.bookmarks.domain.Folder;

/**
 * Implements the default comparison order for the objects involved in a BookmarkSet.
 *
 * Comparison follows these rules in order:
 * Folders are always greater than non Folders.
 * Entry fields are compared in the following order: name, note, created, modified.
 * Bookmark fields are compared in the follwing order:
 *
 * @author Eric Dalquist eric.dalquist@doit.wisc.edu
 * @version $Revision: 12141 $
 */
public class DefaultBookmarksComparator implements Comparator<Entry>, Serializable {
    private static final long serialVersionUID = 1L;

    public static DefaultBookmarksComparator DEFAULT_BOOKMARKS_COMPARATOR = new DefaultBookmarksComparator();

    protected DefaultBookmarksComparator() {
    }

    public int compare(Entry e1, Entry e2) {
        return new CompareToBuilder()
            .appendSuper(this.compareFolders(e1, e2))
            .appendSuper(this.compareEntries(e1, e2))
            .appendSuper(this.compareBookmarks(e1, e2))
            .toComparison();
    }

    /**
     * Folders are always greater than non-Folders, if they are both Folders
     * or both not Folders they are equal.
     */
    protected int compareFolders(final Entry e1, final Entry e2) {
        final boolean f1 = e1 instanceof Folder;
        final boolean f2 = e2 instanceof Folder;

        if (f1 && !f2) {
            return -1;
        }
        else if (!f1 && f2) {
            return 1;
        }
        else {
            return 0;
        }
    }

    /**
     * Compairs the entries by name, note, created and modified properties in that
     * order.
     */
    protected int compareEntries(final Entry e1, final Entry e2) {
        return new CompareToBuilder()
            .append(e1.getName(), e2.getName())
            .append(e1.getNote(), e2.getNote())
            .append(e1.getCreated(), e2.getCreated())
            .append(e1.getModified(), e2.getModified())
            .toComparison();
    }

    /**
     * If both classes are not Bookmarks they are equal. If they are both
     * bookmarks they are compared by url then newWindow properties.
     */
    protected int compareBookmarks(final Entry e1, final Entry e2) {
        if (e1 instanceof Bookmark && e2 instanceof Bookmark) {
            final Bookmark b1 = (Bookmark)e1;
            final Bookmark b2 = (Bookmark)e2;

            return new CompareToBuilder()
                .append(b1.getUrl(), b2.getUrl())
                .append(b1.isNewWindow(), b2.isNewWindow())
                .toComparison();
        }
        else {
            return 0;
        }
    }
}


/**
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License.  You may obtain a
 * copy of the License at the following location:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package edu.wisc.my.portlets.bookmarks.web.support;

import javax.portlet.PortletRequest;

import edu.wisc.my.portlets.bookmarks.dao.PreferencesStore;
import edu.wisc.my.portlets.bookmarks.domain.Preferences;

/**
 * @author Eric Dalquist eric.dalquist@doit.wisc.edu
 * @version $Revision: 12161 $
 */
public class PreferencesRequestResolver {
    protected PreferencesStore preferencesStore;
    protected OwnerResolver ownerResolver;
    protected NameResolver nameResolver;

    /**
     * @return Returns the preferencesStore.
     */
    public PreferencesStore getPreferencesStore() {
        return this.preferencesStore;
    }

    /**
     * @param preferencesStore The preferencesStore to set.
     */
    public void setPreferencesStore(PreferencesStore preferencesStore) {
        this.preferencesStore = preferencesStore;
    }

    /**
     * @return Returns the nameResolver.
     */
    public NameResolver getNameResolver() {
        return this.nameResolver;
    }

    /**
     * @param nameResolver The nameResolver to set.
     */
    public void setNameResolver(NameResolver nameResolver) {
        this.nameResolver = nameResolver;
    }

    /**
     * @return Returns the ownerResolver.
     */
    public OwnerResolver getOwnerResolver() {
        return this.ownerResolver;
    }

    /**
     * @param ownerResolver The ownerResolver to set.
     */
    public void setOwnerResolver(OwnerResolver ownerResolver) {
        this.ownerResolver = ownerResolver;
    }

    /**
     * Calls getPreferences(request, true);
     *
     * @see #getPreferences(PortletRequest, boolean)
     */
    public Preferences getPreferences(PortletRequest request) {
        return this.getPreferences(request, true);
    }

    /**
     * Gets Preferences for the request using the injected {@link OwnerResolver}
     * and {@link NameResolver}.
     *

     *

     * If create is false and no Preferences exists for the name and owner null is returned.
     *

     *

     * If create is true and no Preferences exists for the name and owner a new Preferences is created.
     *
     * @param request The request to resolve the name and owner from.
     * @param create If a Preferences should be created for the name and owner if one does not exist
     * @return The Preferences for the name and owner from the request, null if it does not exists and create is false. If create is true this will never return null.
     */
    public Preferences getPreferences(PortletRequest request, boolean create) {
        final String owner = this.ownerResolver.getOwner(request);
        final String name = this.nameResolver.getBookmarkSetName(request);
        Preferences preferences = this.preferencesStore.getPreferences(owner, name);

        if (preferences == null && create) {
            preferences = this.preferencesStore.createPreferences(owner, name);

            if (preferences == null) {
                throw new IllegalStateException("Required Preferences is null even after createPreferences was called.");
            }
        }

        return preferences;
    }
}


/**
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License.  You may obtain a
 * copy of the License at the following location:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package edu.wisc.my.portlets.bookmarks.domain;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import edu.wisc.my.portlets.bookmarks.domain.support.IntegerSetThreadLocal;

public class CollectionFolder extends Entry implements CollapsibleEntry {
    private static final long serialVersionUID = 1L;

	private static Log log = LogFactory.getLog(CollectionFolder.class);

    private static IntegerSetThreadLocal equalsVisitedFolder = new IntegerSetThreadLocal();
    private static IntegerSetThreadLocal hashCodeVisitedFolder = new IntegerSetThreadLocal();
    private static IntegerSetThreadLocal toStringVisitedFolder = new IntegerSetThreadLocal();

    private Map<Long, Entry> children;
    private boolean minimized = false;
    private String type;
    private String url;

    /**
     * @return Returns the children, will never return null.
     */
    public Map<Long, Entry> getChildren() {
    	// TODO
    	return new HashMap<Long, Entry>();
    }

    /**
     * @return Returns the minimized.
     */
    public boolean isMinimized() {
        return this.minimized;
    }

    /**
     * @param minimized The minimized to set.
     */
    public void setMinimized(boolean minimized) {
        this.minimized = minimized;
    }

    /**
     * Returns an immutable sorted view of the values of the children Map. The sorting is done
     * using the current childComparator. Warning, this is has a time cost of 2n log(n)
     * on every call.
     *
     * @return An immutable sorted view of the folder's children.
     */
    public List<Entry> getSortedChildren() {
    	List<Entry> children = new ArrayList<Entry>();
    	log.debug("children: " + children.size());

		HttpClient client = new HttpClient();
		GetMethod get = null;

		try {

			log.debug("getting url " + url);
			get = new GetMethod(url);
			int rc = client.executeMethod(get);
			if (rc != HttpStatus.SC_OK) {
				log.error("HttpStatus:" + rc);
			}

			DocumentBuilderFactory domBuilderFactory = DocumentBuilderFactory
				.newInstance();
			DocumentBuilder builder = domBuilderFactory.newDocumentBuilder();

			InputStream in = get.getResponseBodyAsStream();
			builder = domBuilderFactory.newDocumentBuilder();
			Document doc = builder.parse(in);
			get.releaseConnection();

			Element e = (Element) doc.getElementsByTagName("rdf:RDF").item(0);
			log.debug("got root " + e);
			NodeList n = e.getElementsByTagName("item");
			log.debug("found items " + n.getLength());
			for (int i = 0; i < n.getLength(); i++) {
				Bookmark bookmark = new Bookmark();
				Element l = (Element) n.item(i);
				bookmark.setName(((Element) l.getElementsByTagName("title").item(0)).getTextContent());
				bookmark.setUrl(((Element) l.getElementsByTagName("link").item(0)).getTextContent());
				if (l.getElementsByTagName("description").getLength() > 0) {
					bookmark.setNote(((Element) l.getElementsByTagName("description").item(0)).getTextContent());
				}
				children.add(bookmark);
				log.debug("added bookmark " + bookmark.getName() + " " + bookmark.getUrl());
			}

		} catch (HttpException e) {
			log.error("Error parsing delicious", e);
		} catch (IOException e) {
			log.error("Error parsing delicious", e);
		} catch (ParserConfigurationException e) {
			log.error("Error parsing delicious", e);
		} catch (SAXException e) {
			log.error("Error parsing delicious", e);
		} finally {
			if (get != null)
				get.releaseConnection();
		}

    	log.debug("children: " + children.size());
    	return children;
    }

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

}


/*******************************************************************************
 * Copyright (c) 2015, 2016 IBH SYSTEMS GmbH.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBH SYSTEMS GmbH - initial API and implementation
 *******************************************************************************/
package org.eclipse.packagedrone.repo.channel;

import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;

public class ChannelId
{
    private final String id;

    private final Set<String> names;

    private final String description;

    public ChannelId ( final String id )
    {
        this ( id, Collections.emptySet (), null );
    }

    public ChannelId ( final String id, final Set<String> names, final String description )
    {
        Objects.requireNonNull ( id );
        Objects.requireNonNull ( names );

        this.id = id;
        this.names = Collections.unmodifiableSet ( new LinkedHashSet<> ( names ) );

        if ( this.id == null )
        {
            throw new NullPointerException ( "'id' must not be null" );
        }

        this.description = description;
    }

    /**
     * Get the channel id
     *

     * This is the internal, immutable ID of the channel
     *

     *
     * @return the channel ID, never {@code null}
     */
    public String getId ()
    {
        return this.id;
    }

    /**
     * Get the alias names of the channel
     *

     * Many operations do allows accessing the channel by using an alias,
     * instead of the internal ID. However the alias list can be changed and
     * aliases may be re-assigned to another channel. The result may be an empty
     * set.
     *

     *
     * @return the set or alias names, never {@code null}
     */
    public Set<String> getNames ()
    {
        return this.names;
    }

    /**
     * Get the optional description
     *
     * @return the description, or {@code null} if none is set
     */
    public String getDescription ()
    {
        return this.description;
    }

    /**
     * Get the optional short description
     *

     * The short description is the first line of {@link #getDescription()}
     *

     *
     * @return the short description, or {@code null} if none is set
     */
    public String getShortDescription ()
    {
        if ( this.description == null )
        {
            return null;
        }

        final String[] lines = this.description.split ( "\\R", 2 );
        if ( lines.length > 0 )
        {
            return lines[0];
        }
        else
        {
            return "";
        }
    }

    public String makeTitle ()
    {
        final String desc = this.description;
        if ( desc != null && !desc.isEmpty () )
        {
            return String.format ( "%s (%s)", this.id, desc );
        }
        else
        {
            return this.id;
        }
    }

    // public static Comparator<? super ChannelId> NAME_COMPARATOR = Comparator.comparing ( ChannelId::getName, Comparator.nullsLast ( Comparator.naturalOrder () ) ).thenComparing ( Comparator.comparing ( ChannelId::getId ) );
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 *
 *
 * This file incorporates work covered by the following copyright and
 * permission notice:
 *
 * Copyright 2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jasper.servlet;

// START PWC 6468930
import java.io.File;
// END PWC 6468930
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.logging.Logger;
import java.util.logging.Level;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.SingleThreadModel;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.tagext.TagInfo;

import org.glassfish.jsp.api.JspProbeEmitter;

import org.apache.jasper.JasperException;
import org.apache.jasper.JspCompilationContext;
import org.apache.jasper.Options;
import org.apache.jasper.compiler.JspRuntimeContext;
import org.apache.jasper.compiler.Localizer;
import org.apache.jasper.runtime.JspSourceDependent;

/**
 * The JSP engine (a.k.a Jasper).
 *
 * The servlet container is responsible for providing a
 * URLClassLoader for the web application context Jasper
 * is being used in. Jasper will try get the Tomcat
 * ServletContext attribute for its ServletContext class
 * loader, if that fails, it uses the parent class loader.
 * In either case, it must be a URLClassLoader.
 *
 * @author Anil K. Vijendran
 * @author Harish Prabandham
 * @author Remy Maucherat
 * @author Kin-man Chung
 * @author Glenn Nielsen
 */

public class JspServletWrapper {

    // Logger
    private static Logger log =
            Logger.getLogger(JspServletWrapper.class.getName());

    private Servlet theServlet;
    private String jspUri;
    private Class servletClass;
    private Class tagHandlerClass;
    private JspCompilationContext ctxt;
    private long available = 0L;
    private ServletConfig config;
    private Options options;
    private boolean firstTime = true;
    private boolean reload = true;
    private boolean isTagFile;
    private int tripCount;
    private JasperException compileException;
    private JspProbeEmitter jspProbeEmitter;
    /* PWC 6468930
    private long servletClassLastModifiedTime;
    */
    // START PWC 6468930
    private long servletClassLastModifiedTime = 0L;
    private File jspFile = null;
    // END PWC 6468930
    private long lastModificationTest = 0L;

    /*
     * JspServletWrapper for JSP pages.
     */
    JspServletWrapper(ServletConfig config, Options options, String jspUri,
                      boolean isErrorPage, JspRuntimeContext rctxt)
            throws JasperException {

	this.isTagFile = false;
        this.config = config;
        this.options = options;
        this.jspUri = jspUri;
        this.jspProbeEmitter = (JspProbeEmitter)
            config.getServletContext().getAttribute(
                "org.glassfish.jsp.monitor.probeEmitter");

        ctxt = new JspCompilationContext(jspUri, isErrorPage, options,
					 config.getServletContext(),
					 this, rctxt);
        // START PWC 6468930
        String jspFilePath = ctxt.getRealPath(jspUri);
        if (jspFilePath != null) {
            jspFile = new File(jspFilePath);
        }
        // END PWC 6468930
    }

    /*
     * JspServletWrapper for tag files.
     */
    public JspServletWrapper(ServletContext servletContext,
			     Options options,
			     String tagFilePath,
			     TagInfo tagInfo,
			     JspRuntimeContext rctxt,
			     URL tagFileJarUrl)
	    throws JasperException {

	this.isTagFile = true;
        this.config = null;	// not used
        this.options = options;
	this.jspUri = tagFilePath;
	this.tripCount = 0;
        ctxt = new JspCompilationContext(jspUri, tagInfo, options,
					 servletContext, this, rctxt,
					 tagFileJarUrl);
    }

    public JspCompilationContext getJspEngineContext() {
        return ctxt;
    }

    public void setReload(boolean reload) {
        this.reload = reload;
    }

    public Servlet getServlet()
        throws ServletException, IOException, ClassNotFoundException
    {
        if (reload) {
            synchronized (this) {
                // Synchronizing on jsw enables simultaneous loading
                // of different pages, but not the same page.
                if (reload) {
                    // This is to maintain the original protocol.
                    destroy();

                    try {
                        servletClass = ctxt.load();
                        theServlet = (Servlet) servletClass.newInstance();
                    } catch( IllegalAccessException ex1 ) {
                        throw new JasperException( ex1 );
                    } catch( InstantiationException ex ) {
                        throw new JasperException( ex );
                    }

                    theServlet.init(config);

                    if (!firstTime) {
                        ctxt.getRuntimeContext().incrementJspReloadCount();
                        // Fire the jspReloadedEvent probe event
                        if (jspProbeEmitter != null) {
                            jspProbeEmitter.jspReloadedEvent(jspUri);
                        }
                    }

                    reload = false;

                    // Fire the jspLoadedEvent probe event
                    if (jspProbeEmitter != null) {
                        jspProbeEmitter.jspLoadedEvent(jspUri);
                    }
                }
            }
        }
        return theServlet;
    }

    public ServletContext getServletContext() {
        return config.getServletContext();
    }

    /**
     * Sets the compilation exception for this JspServletWrapper.
     *
     * @param je The compilation exception
     */
    public void setCompilationException(JasperException je) {
        this.compileException = je;
    }

    /**
     * Sets the last-modified time of the servlet class file associated with
     * this JspServletWrapper.
     *
     * @param lastModified Last-modified time of servlet class
     */
    public void setServletClassLastModifiedTime(long lastModified) {
        if (this.servletClassLastModifiedTime < lastModified) {
            synchronized (this) {
                if (this.servletClassLastModifiedTime < lastModified) {
                    this.servletClassLastModifiedTime = lastModified;
                    reload = true;
                }
            }
        }
    }

    // START CR 6373479
    /**
     * Gets the last-modified time of the servlet class file associated with
     * this JspServletWrapper.
     *
     * @return Last-modified time of servlet class
     */
    public long getServletClassLastModifiedTime() {
        return servletClassLastModifiedTime;
    }
    // END CR 6373479

    /**
     * Compile (if needed) and load a tag file
     */
    public Class loadTagFile() throws JasperException {

        try {
            ctxt.compile();
            if (reload) {
                tagHandlerClass = ctxt.load();
            }
        } catch (ClassNotFoundException ex) {
        } catch (FileNotFoundException ex) {
            log.log(Level.SEVERE, Localizer.getMessage("jsp.error.compiling"));
            throw new JasperException(ex);
	}
	return tagHandlerClass;
    }

    /**
     * Compile and load a prototype for the Tag file.  This is needed
     * when compiling tag files with circular dependencies.  A prototpe
     * (skeleton) with no dependencies on other other tag files is
     * generated and compiled.
     */
    public Class loadTagFilePrototype() throws JasperException {

	ctxt.setPrototypeMode(true);
	try {
	    return loadTagFile();
	} finally {
	    ctxt.setPrototypeMode(false);
	}
    }

    /**
     * Get a list of files that the current page has source dependency on.
     */
    public java.util.List<String> getDependants() {
	try {
	    Object target;
	    if (isTagFile) {
                if (reload) {
                    tagHandlerClass = ctxt.load();
                }
		target = tagHandlerClass.newInstance();
	    } else {
		target = getServlet();
	    }
	    if (target != null && target instanceof JspSourceDependent) {
                return ((JspSourceDependent) target).getDependants();
	    }
	} catch (Throwable ex) {
	}
	return null;
    }

    public boolean isTagFile() {
	return this.isTagFile;
    }

    public int incTripCount() {
	return tripCount++;
    }

    public int decTripCount() {
	return tripCount--;
    }

    public void service(HttpServletRequest request,
                        HttpServletResponse response,
                        boolean precompile)
	    throws ServletException, IOException {

        try {

            if (ctxt.isRemoved()) {
                jspFileNotFound(request, response);
                return;
            }

            if ((available > 0L) && (available < Long.MAX_VALUE)) {
                response.setDateHeader("Retry-After", available);
                response.sendError
                    (HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                     Localizer.getMessage("jsp.error.unavailable"));
            }

            /*
             * (1) Compile
             */
            // BEGIN S1AS 6181923
            // if (options.getDevelopment() || firstTime) {
            // END S1AS 6181923
            // BEGIN S1AS 6181923
            if (!options.getUsePrecompiled()
                    && (options.getDevelopment() || firstTime)) {
            // END S1AS 6181923
                synchronized (this) {
                    firstTime = false;

                    // The following sets reload to true, if necessary
                    ctxt.compile();
                }
            } else {
                if (compileException != null) {
                    // Throw cached compilation exception
                    throw compileException;
                }
            }

            /*
             * (2) (Re)load servlet class file
             */
            try {
                getServlet();
            } catch (ClassNotFoundException ex) {
                // This can only happen when use-precomiled is set and a
                // supposedly pre-compiled class does not exist.
                jspFileNotFound(request, response);
                return;
            }

            // If a page is to be precompiled only, return.
            if (precompile) {
                return;
            }

            /*
             * (3) Service request
             */
            if (theServlet instanceof SingleThreadModel) {
               // sync on the wrapper so that the freshness
               // of the page is determined right before servicing
               synchronized (this) {
                   theServlet.service(request, response);
                }
            } else {
                theServlet.service(request, response);
            }

        } catch (UnavailableException ex) {
            String includeRequestUri = (String)
                request.getAttribute("javax.servlet.include.request_uri");
            if (includeRequestUri != null) {
                // This file was included. Throw an exception as
                // a response.sendError() will be ignored by the
                // servlet engine.
                throw ex;
            } else {
                int unavailableSeconds = ex.getUnavailableSeconds();
                if (unavailableSeconds <= 0) {
                    unavailableSeconds = 60;        // Arbitrary default
                }
                available = System.currentTimeMillis() +
                    (unavailableSeconds * 1000L);
                response.sendError
                    (HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                     ex.getMessage());
            }
        } catch (ServletException ex) {
	    throw ex;
        } catch (IOException ex) {
            throw ex;
        } catch (IllegalStateException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new JasperException(ex);
        }
    }

    public void destroy() {
        if (theServlet != null) {
            theServlet.destroy();
            // Fire the jspDestroyedEvent probe event
            if (jspProbeEmitter != null) {
                jspProbeEmitter.jspDestroyedEvent(jspUri);
            }
        }
    }

    /**
     * @return Returns the lastModificationTest.
     */
    public long getLastModificationTest() {
        return lastModificationTest;
    }

    /**
     * @param lastModificationTest The lastModificationTest to set.
     */
    public void setLastModificationTest(long lastModificationTest) {
        this.lastModificationTest = lastModificationTest;
    }

    // START PWC 6468930
    public File getJspFile() {
        return jspFile;
    }
    // END PWC 6468930

    /*
     * Handles the case where a requested JSP file no longer exists.
     */
    private void jspFileNotFound(HttpServletRequest request,
                                 HttpServletResponse response)
            throws ServletException, IOException {

        FileNotFoundException fnfe = new FileNotFoundException(jspUri);

        ctxt.incrementRemoved();
        String includeRequestUri = (String)
            request.getAttribute("javax.servlet.include.request_uri");
        if (includeRequestUri != null) {
            // This file was included. Throw an exception as
            // a response.sendError() will be ignored by the
            // servlet engine.
            throw new ServletException(fnfe);
        } else {
            try {
                response.sendError(HttpServletResponse.SC_NOT_FOUND,
                                   fnfe.getMessage());
            } catch (IllegalStateException ise) {
                log.log(Level.SEVERE,
                        Localizer.getMessage("jsp.error.file.not.found",
                                             fnfe.getMessage()),
                        fnfe);
            }
        }
    }

}