All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.mail.util.logging.DurationFilter Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2015-2017 Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2015-2017 Jason Mehrens. 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://oss.oracle.com/licenses/CDDL+GPL-1.1
 * or 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 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.
 */
package com.sun.mail.util.logging;

import static com.sun.mail.util.logging.LogManagerProperties.fromLogManager;
import java.util.logging.*;

/**
 * A filter used to limit log records based on a maximum generation rate.
 *
 * The duration specified is used to compute the record rate and the amount of
 * time the filter will reject records once the rate has been exceeded. Once the
 * rate is exceeded records are not allowed until the duration has elapsed.
 *
 * 

* By default each {@code DurationFilter} is initialized using the following * LogManager configuration properties where {@code } refers to the * fully qualified class name of the handler. If properties are not defined, or * contain invalid values, then the specified default values are used. * *

    *
  • {@literal }.records the max number of records per duration. * A numeric long integer or a multiplication expression can be used as the * value. (defaults to {@code 1000}) * *
  • {@literal }.duration the number of milliseconds to suppress * log records from being published. This is also used as duration to determine * the log record rate. A numeric long integer or a multiplication expression * can be used as the value. If the {@code java.time} package is available then * an ISO-8601 duration format of {@code PnDTnHnMn.nS} can be used as the value. * The suffixes of "D", "H", "M" and "S" are for days, hours, minutes and * seconds. The suffixes must occur in order. The seconds can be specified with * a fractional component to declare milliseconds. (defaults to {@code PT15M}) *
* *

* For example, the settings to limit {@code MailHandler} with a default * capacity to only send a maximum of two email messages every six minutes would * be as follows: *

 * {@code
 *  com.sun.mail.util.logging.MailHandler.filter = com.sun.mail.util.logging.DurationFilter
 *  com.sun.mail.util.logging.MailHandler.capacity = 1000
 *  com.sun.mail.util.logging.DurationFilter.records = 2L * 1000L
 *  com.sun.mail.util.logging.DurationFilter.duration = PT6M
 * }
 * 
* * * @author Jason Mehrens * @since JavaMail 1.5.5 */ public class DurationFilter implements Filter { /** * The number of expected records per duration. */ private final long records; /** * The duration in milliseconds used to determine the rate. The duration is * also used as the amount of time that the filter will not allow records * when saturated. */ private final long duration; /** * The number of records seen for the current duration. This value negative * if saturated. Zero is considered saturated but is reserved for recording * the first duration. */ private long count; /** * The most recent record time seen for the current duration. */ private long peak; /** * The start time for the current duration. */ private long start; /** * Creates the filter using the default properties. */ public DurationFilter() { this.records = checkRecords(initLong(".records")); this.duration = checkDuration(initLong(".duration")); } /** * Creates the filter using the given properties. Default values are used if * any of the given values are outside the allowed range. * * @param records the number of records per duration. * @param duration the number of milliseconds to suppress log records from * being published. */ public DurationFilter(final long records, final long duration) { this.records = checkRecords(records); this.duration = checkDuration(duration); } /** * Determines if this filter is equal to another filter. * * @param obj the given object. * @return true if equal otherwise false. */ @Override public boolean equals(final Object obj) { if (this == obj) { //Avoid locks and deal with rapid state changes. return true; } if (obj == null || getClass() != obj.getClass()) { return false; } final DurationFilter other = (DurationFilter) obj; if (this.records != other.records) { return false; } if (this.duration != other.duration) { return false; } final long c; final long p; final long s; synchronized (this) { c = this.count; p = this.peak; s = this.start; } synchronized (other) { if (c != other.count || p != other.peak || s != other.start) { return false; } } return true; } /** * Determines if this filter is able to accept the maximum number of log * records for this instant in time. The result is a best-effort estimate * and should be considered out of date as soon as it is produced. This * method is designed for use in monitoring the state of this filter. * * @return true if the filter is idle; false otherwise. */ public boolean isIdle() { return test(0L, System.currentTimeMillis()); } /** * Returns a hash code value for this filter. * * @return hash code for this filter. */ @Override public int hashCode() { int hash = 3; hash = 89 * hash + (int) (this.records ^ (this.records >>> 32)); hash = 89 * hash + (int) (this.duration ^ (this.duration >>> 32)); return hash; } /** * Check if the given log record should be published. This method will * modify the internal state of this filter. * * @param record the log record to check. * @return true if allowed; false otherwise. * @throws NullPointerException if given record is null. */ @SuppressWarnings("override") //JDK-6954234 public boolean isLoggable(final LogRecord record) { return accept(record.getMillis()); } /** * Determines if this filter will accept log records for this instant in * time. The result is a best-effort estimate and should be considered out * of date as soon as it is produced. This method is designed for use in * monitoring the state of this filter. * * @return true if the filter is not saturated; false otherwise. */ public boolean isLoggable() { return test(records, System.currentTimeMillis()); } /** * Returns a string representation of this filter. The result is a * best-effort estimate and should be considered out of date as soon as it * is produced. * * @return a string representation of this filter. */ @Override public String toString() { boolean idle; boolean loggable; synchronized (this) { final long millis = System.currentTimeMillis(); idle = test(0L, millis); loggable = test(records, millis); } return getClass().getName() + "{records=" + records + ", duration=" + duration + ", idle=" + idle + ", loggable=" + loggable + '}'; } /** * Creates a copy of this filter that retains the filter settings but does * not include the current filter state. The newly create clone acts as if * it has never seen any records. * * @return a copy of this filter. * @throws CloneNotSupportedException if this filter is not allowed to be * cloned. */ @Override protected DurationFilter clone() throws CloneNotSupportedException { final DurationFilter clone = (DurationFilter) super.clone(); clone.count = 0L; //Reset the filter state. clone.peak = 0L; clone.start = 0L; return clone; } /** * Checks if this filter is not saturated or bellow a maximum rate. * * @param limit the number of records allowed to be under the rate. * @param millis the current time in milliseconds. * @return true if not saturated or bellow the rate. */ private boolean test(final long limit, final long millis) { assert limit >= 0L : limit; final long c; final long s; synchronized (this) { c = count; s = start; } if (c > 0L) { //If not saturated. if ((millis - s) >= duration || c < limit) { return true; } } else { //Subtraction is used to deal with numeric overflow. if ((millis - s) >= 0L || c == 0L) { return true; } } return false; } /** * Determines if the record is loggable by time. * * @param millis the log record milliseconds. * @return true if accepted false otherwise. */ private synchronized boolean accept(final long millis) { //Subtraction is used to deal with numeric overflow of millis. boolean allow; if (count > 0L) { //If not saturated. if ((millis - peak) > 0L) { peak = millis; //Record the new peak. } //Under the rate if the count has not been reached. if (count != records) { ++count; allow = true; } else { if ((peak - start) >= duration) { count = 1L; //Start a new duration. start = peak; allow = true; } else { count = -1L; //Saturate for the duration. start = peak + duration; allow = false; } } } else { //If the saturation period has expired or this is the first record //then start a new duration and allow records. if ((millis - start) >= 0L || count == 0L) { count = 1L; start = millis; peak = millis; allow = true; } else { allow = false; //Remain in a saturated state. } } return allow; } /** * Reads a long value or multiplication expression from the LogManager. If * the value can not be parsed or is not defined then Long.MIN_VALUE is * returned. * * @param suffix a dot character followed by the key name. * @return a long value or Long.MIN_VALUE if unable to parse or undefined. * @throws NullPointerException if suffix is null. */ private long initLong(final String suffix) { long result = 0L; final String p = getClass().getName(); String value = fromLogManager(p.concat(suffix)); if (value != null && value.length() != 0) { value = value.trim(); if (isTimeEntry(suffix, value)) { try { result = LogManagerProperties.parseDurationToMillis(value); } catch (final RuntimeException ignore) { } catch (final Exception ignore) { } catch (final LinkageError ignore) { } } if (result == 0L) { //Zero is invalid. try { result = 1L; for (String s : tokenizeLongs(value)) { if (s.endsWith("L") || s.endsWith("l")) { s = s.substring(0, s.length() - 1); } result = multiplyExact(result, Long.parseLong(s)); } } catch (final RuntimeException ignore) { result = Long.MIN_VALUE; } } } else { result = Long.MIN_VALUE; } return result; } /** * Determines if the given suffix can be a time unit and the value is * encoded as an ISO ISO-8601 duration format. * * @param suffix the suffix property. * @param value the value of the property. * @return true if the entry is a time entry. * @throws IndexOutOfBoundsException if value is empty. * @throws NullPointerException if either argument is null. */ private boolean isTimeEntry(final String suffix, final String value) { return (value.charAt(0) == 'P' || value.charAt(0) == 'p') && suffix.equals(".duration"); } /** * Parse any long value or multiplication expressions into tokens. * * @param value the expression or value. * @return an array of long tokens, never empty. * @throws NullPointerException if the given value is null. * @throws NumberFormatException if the expression is invalid. */ private static String[] tokenizeLongs(final String value) { String[] e; final int i = value.indexOf('*'); if (i > -1 && (e = value.split("\\s*\\*\\s*")).length != 0) { if (i == 0 || value.charAt(value.length() - 1) == '*') { throw new NumberFormatException(value); } if (e.length == 1) { throw new NumberFormatException(e[0]); } } else { e = new String[]{value}; } return e; } /** * Multiply and check for overflow. This can be replaced with * {@code java.lang.Math.multiplyExact} when JavaMail requires JDK 8. * * @param x the first value. * @param y the second value. * @return x times y. * @throws ArithmeticException if overflow is detected. */ private static long multiplyExact(final long x, final long y) { long r = x * y; if (((Math.abs(x) | Math.abs(y)) >>> 31L != 0L)) { if (((y != 0L) && (r / y != x)) || (x == Long.MIN_VALUE && y == -1L)) { throw new ArithmeticException(); } } return r; } /** * Converts record count to a valid record count. If the value is out of * bounds then the default record count is used. * * @param records the record count. * @return a valid number of record count. */ private static long checkRecords(final long records) { return records > 0L ? records : 1000L; } /** * Converts the duration to a valid duration. If the value is out of bounds * then the default duration is used. * * @param duration the duration to check. * @return a valid duration. */ private static long checkDuration(final long duration) { return duration > 0L ? duration : 15L * 60L * 1000L; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy