<commit_before>/**
 * Copyright (C) 2014 Orbeon, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either version
 * 2.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.servlet

import java.util.concurrent.Semaphore
import javax.servlet._
import javax.servlet.http.HttpServletRequest

import org.orbeon.oxf.util.NetUtils
import org.orbeon.oxf.util.ScalaUtils._
import org.slf4j.LoggerFactory

import scala.util.matching.Regex

class LimiterFilter extends Filter {

    import LimiterFilter._

    private case class FilterSettings(semaphore: Semaphore, include: Regex, exclude: Regex)

    private var settingsOpt: Option[FilterSettings] = None

    override def init(config: FilterConfig) = {

        val limit = desiredParallelism(config.getInitParameter)

        Logger.info("initializing limiter filter")

        val settings =
            FilterSettings(
                new Semaphore(limit, true),
                (nonEmptyOrNone(config.getInitParameter("include")) getOrElse "$.").r,
                (nonEmptyOrNone(config.getInitParameter("exclude")) getOrElse "$.").r
            )

        Logger.info(s"limiter filter configuration: $settings")

        settingsOpt = Some(settings)
    }

    override def destroy() = {
        Logger.info(s"destroying limiter filter")
        settingsOpt = None
    }

    override def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit =
        settingsOpt foreach { case FilterSettings(semaphore, include, exclude) ⇒

            val httpReq = req.asInstanceOf[HttpServletRequest]

            NetUtils.getRequestPathInfo(httpReq) match {
                case path if include.pattern.matcher(path).matches && ! exclude.pattern.matcher(path).matches ⇒
                    Logger.debug(s"before acquiring semaphore for '$path'")
                    val timestamp = if (Logger.isDebugEnabled) System.nanoTime else -1
                    semaphore.acquire()
                    try {
                        Logger.debug(s"after acquiring semaphore for '$path' (waited ${(System.nanoTime - timestamp) / 1000000} ms)")
                        chain.doFilter(req, res)
                    }  finally
                        semaphore.release()
                case path ⇒
                    Logger.debug(s"skipping semaphore for '$path'")
                    chain.doFilter(req, res)<commit_msg>LimiterFilter: comments and minor changes<commit_after>/**
 * Copyright (C) 2014 Orbeon, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either version
 * 2.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.servlet

import java.util.concurrent.Semaphore
import javax.servlet._
import javax.servlet.http.HttpServletRequest

import org.orbeon.oxf.util.NetUtils
import org.orbeon.oxf.util.ScalaUtils._
import org.slf4j.LoggerFactory

import scala.util.matching.Regex

// Servlet filter to limit the number of concurrent threads entering the filter chain
//
// Features:
//
// - paths to include/exclude can be configured with regular expressions
// - the minimum, requested, and maximum number of concurrent threads allowed can be configured
//
// By default, the filter sets a limit to the number of CPU cores returned by the JVM. This includes hyper-threading,
// see: http://stackoverflow.com/questions/11738133/is-it-possible-to-check-in-java-if-the-cpu-is-hyper-threading
//
// The threads configuration can be set to multiples of the number of available cores with the `x` prefix, for example:
//
// - x0.5
// - x1.25
class LimiterFilter extends Filter {

    import LimiterFilter.Logger._

    private case class FilterSettings(semaphore: Semaphore, include: Regex, exclude: Regex)

    private var settingsOpt: Option[FilterSettings] = None

    override def init(config: FilterConfig) = {

        val limit = desiredParallelism(config.getInitParameter)

        info("initializing limiter filter")

        val settings =
            FilterSettings(
                new Semaphore(limit, true),
                (nonEmptyOrNone(config.getInitParameter("include")) getOrElse "$.").r,
                (nonEmptyOrNone(config.getInitParameter("exclude")) getOrElse "$.").r
            )

        info(s"limiter filter configuration: $settings")

        settingsOpt = Some(settings)
    }

    override def destroy() = {
        info(s"destroying limiter filter")
        settingsOpt = None
    }

    override def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit =
        settingsOpt foreach { case FilterSettings(semaphore, include, exclude) ⇒

            val httpReq = req.asInstanceOf[HttpServletRequest]

            NetUtils.getRequestPathInfo(httpReq) match {
                case path if include.pattern.matcher(path).matches && ! exclude.pattern.matcher(path).matches ⇒
                    debug(s"semaphore: before acquiring for '$path'")
                    val timestamp = if (isDebugEnabled) System.nanoTime else -1
                    semaphore.acquire()
                    try {
                        debug(s"semaphore: acquired for '$path' in ${(System.nanoTime - timestamp) / 1000000} ms")
                        chain.doFilter(req, res)
                    }  finally
                        semaphore.release()
                case path ⇒
                    debug(s"semaphore: skipping for '$path'")
                    chain.doFilter(req, res)
{'fraction_non_alphanumeric': 0.0868207370393504, 'fraction_numerical': 0.006714553404122424, 'mean_word_length': 2.633011911514464, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3105925', 'n_tokens_mistral': 1756, 'n_tokens_neox': 1662, 'n_words': 548}