Skip to content

Package: DurationFilter

DurationFilter

nameinstructionbranchcomplexitylinemethod
DurationFilter()
M: 15 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
DurationFilter(long, long)
M: 11 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
accept(long)
M: 90 C: 0
0%
M: 12 C: 0
0%
M: 7 C: 0
0%
M: 20 C: 0
0%
M: 1 C: 0
0%
checkDuration(long)
M: 8 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
checkRecords(long)
M: 8 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
clone()
M: 15 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 5 C: 0
0%
M: 1 C: 0
0%
equals(Object)
M: 77 C: 0
0%
M: 16 C: 0
0%
M: 9 C: 0
0%
M: 19 C: 0
0%
M: 1 C: 0
0%
hashCode()
M: 30 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
initLong(String)
M: 86 C: 0
0%
M: 14 C: 0
0%
M: 8 C: 0
0%
M: 22 C: 0
0%
M: 1 C: 0
0%
isIdle()
M: 5 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
isLoggable()
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
isLoggable(LogRecord)
M: 5 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
isTimeEntry(String, String)
M: 18 C: 0
0%
M: 6 C: 0
0%
M: 4 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
multiplyExact(long, long)
M: 38 C: 0
0%
M: 10 C: 0
0%
M: 6 C: 0
0%
M: 5 C: 0
0%
M: 1 C: 0
0%
static {...}
M: 1 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
test(long, long)
M: 54 C: 0
0%
M: 12 C: 0
0%
M: 7 C: 0
0%
M: 11 C: 0
0%
M: 1 C: 0
0%
toString()
M: 49 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 6 C: 0
0%
M: 1 C: 0
0%
tokenizeLongs(String)
M: 49 C: 0
0%
M: 10 C: 0
0%
M: 6 C: 0
0%
M: 8 C: 0
0%
M: 1 C: 0
0%

Coverage

1: /*
2: * Copyright (c) 2015, 2023 Oracle and/or its affiliates. All rights reserved.
3: * Copyright (c) 2015, 2023 Jason Mehrens. All rights reserved.
4: *
5: * This program and the accompanying materials are made available under the
6: * terms of the Eclipse Public License v. 2.0, which is available at
7: * http://www.eclipse.org/legal/epl-2.0.
8: *
9: * This Source Code may also be made available under the following Secondary
10: * Licenses when the conditions for such availability set forth in the
11: * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
12: * version 2 with the GNU Classpath Exception, which is available at
13: * https://www.gnu.org/software/classpath/license.html.
14: *
15: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
16: */
17:
18: package org.eclipse.angus.mail.util.logging;
19:
20: import static org.eclipse.angus.mail.util.logging.LogManagerProperties.fromLogManager;
21: import java.util.logging.*;
22:
23: /**
24: * A filter used to limit log records based on a maximum generation rate.
25: *
26: * The duration specified is used to compute the record rate and the amount of
27: * time the filter will reject records once the rate has been exceeded. Once the
28: * rate is exceeded records are not allowed until the duration has elapsed.
29: *
30: * <p>
31: * By default each {@code DurationFilter} is initialized using the following
32: * LogManager configuration properties where {@code <filter-name>} refers to the
33: * fully qualified class name of the handler. If properties are not defined, or
34: * contain invalid values, then the specified default values are used.
35: *
36: * <ul>
37: * <li>{@literal <filter-name>}.records the max number of records per duration.
38: * A numeric long integer or a multiplication expression can be used as the
39: * value. (defaults to {@code 1000})
40: *
41: * <li>{@literal <filter-name>}.duration the number of milliseconds to suppress
42: * log records from being published. This is also used as duration to determine
43: * the log record rate. A numeric long integer or a multiplication expression
44: * can be used as the value. If the {@code java.time} package is available then
45: * an ISO-8601 duration format of {@code PnDTnHnMn.nS} can be used as the value.
46: * The suffixes of "D", "H", "M" and "S" are for days, hours, minutes and
47: * seconds. The suffixes must occur in order. The seconds can be specified with
48: * a fractional component to declare milliseconds. (defaults to {@code PT15M})
49: * </ul>
50: *
51: * <p>
52: * For example, the settings to limit {@code MailHandler} with a default
53: * capacity to only send a maximum of two email messages every six minutes would
54: * be as follows:
55: * <pre>
56: * {@code
57: * org.eclipse.angus.mail.util.logging.MailHandler.filter = org.eclipse.angus.mail.util.logging.DurationFilter
58: * org.eclipse.angus.mail.util.logging.MailHandler.capacity = 1000
59: * org.eclipse.angus.mail.util.logging.DurationFilter.records = 2L * 1000L
60: * org.eclipse.angus.mail.util.logging.DurationFilter.duration = PT6M
61: * }
62: * </pre>
63: *
64: *
65: * @author Jason Mehrens
66: * @since JavaMail 1.5.5
67: */
68: public class DurationFilter implements Filter {
69:
70: /**
71: * The number of expected records per duration.
72: */
73: private final long records;
74: /**
75: * The duration in milliseconds used to determine the rate. The duration is
76: * also used as the amount of time that the filter will not allow records
77: * when saturated.
78: */
79: private final long duration;
80: /**
81: * The number of records seen for the current duration. This value negative
82: * if saturated. Zero is considered saturated but is reserved for recording
83: * the first duration.
84: */
85: private long count;
86: /**
87: * The most recent record time seen for the current duration.
88: */
89: private long peak;
90: /**
91: * The start time for the current duration.
92: */
93: private long start;
94:
95: /**
96: * Creates the filter using the default properties.
97: */
98: public DurationFilter() {
99: this.records = checkRecords(initLong(".records"));
100: this.duration = checkDuration(initLong(".duration"));
101: }
102:
103: /**
104: * Creates the filter using the given properties. Default values are used if
105: * any of the given values are outside the allowed range.
106: *
107: * @param records the number of records per duration.
108: * @param duration the number of milliseconds to suppress log records from
109: * being published.
110: */
111: public DurationFilter(final long records, final long duration) {
112: this.records = checkRecords(records);
113: this.duration = checkDuration(duration);
114: }
115:
116: /**
117: * Determines if this filter is equal to another filter.
118: *
119: * @param obj the given object.
120: * @return true if equal otherwise false.
121: */
122: @Override
123: public boolean equals(final Object obj) {
124:• if (this == obj) { //Avoid locks and deal with rapid state changes.
125: return true;
126: }
127:
128:• if (obj == null || getClass() != obj.getClass()) {
129: return false;
130: }
131:
132: final DurationFilter other = (DurationFilter) obj;
133:• if (this.records != other.records) {
134: return false;
135: }
136:
137:• if (this.duration != other.duration) {
138: return false;
139: }
140:
141: final long c;
142: final long p;
143: final long s;
144: synchronized (this) {
145: c = this.count;
146: p = this.peak;
147: s = this.start;
148: }
149:
150: synchronized (other) {
151:• if (c != other.count || p != other.peak || s != other.start) {
152: return false;
153: }
154: }
155: return true;
156: }
157:
158: /**
159: * Determines if this filter is able to accept the maximum number of log
160: * records for this instant in time. The result is a best-effort estimate
161: * and should be considered out of date as soon as it is produced. This
162: * method is designed for use in monitoring the state of this filter.
163: *
164: * @return true if the filter is idle; false otherwise.
165: */
166: public boolean isIdle() {
167: return test(0L, System.currentTimeMillis());
168: }
169:
170: /**
171: * Returns a hash code value for this filter.
172: *
173: * @return hash code for this filter.
174: */
175: @Override
176: public int hashCode() {
177: int hash = 3;
178: hash = 89 * hash + (int) (this.records ^ (this.records >>> 32));
179: hash = 89 * hash + (int) (this.duration ^ (this.duration >>> 32));
180: return hash;
181: }
182:
183: /**
184: * Check if the given log record should be published. This method will
185: * modify the internal state of this filter.
186: *
187: * @param record the log record to check.
188: * @return true if allowed; false otherwise.
189: * @throws NullPointerException if given record is null.
190: */
191: @SuppressWarnings("override") //JDK-6954234
192: public boolean isLoggable(final LogRecord record) {
193: return accept(record.getMillis());
194: }
195:
196: /**
197: * Determines if this filter will accept log records for this instant in
198: * time. The result is a best-effort estimate and should be considered out
199: * of date as soon as it is produced. This method is designed for use in
200: * monitoring the state of this filter.
201: *
202: * @return true if the filter is not saturated; false otherwise.
203: */
204: public boolean isLoggable() {
205: return test(records, System.currentTimeMillis());
206: }
207:
208: /**
209: * Returns a string representation of this filter. The result is a
210: * best-effort estimate and should be considered out of date as soon as it
211: * is produced.
212: *
213: * @return a string representation of this filter.
214: */
215: @Override
216: public String toString() {
217: boolean idle;
218: boolean loggable;
219: synchronized (this) {
220: final long millis = System.currentTimeMillis();
221: idle = test(0L, millis);
222: loggable = test(records, millis);
223: }
224:
225: return getClass().getName() + "{records=" + records
226: + ", duration=" + duration
227: + ", idle=" + idle
228: + ", loggable=" + loggable + '}';
229: }
230:
231: /**
232: * Creates a copy of this filter that retains the filter settings but does
233: * not include the current filter state. The newly create clone acts as if
234: * it has never seen any records.
235: *
236: * @return a copy of this filter.
237: * @throws CloneNotSupportedException if this filter is not allowed to be
238: * cloned.
239: */
240: @Override
241: protected DurationFilter clone() throws CloneNotSupportedException {
242: final DurationFilter clone = (DurationFilter) super.clone();
243: clone.count = 0L; //Reset the filter state.
244: clone.peak = 0L;
245: clone.start = 0L;
246: return clone;
247: }
248:
249: /**
250: * Checks if this filter is not saturated or bellow a maximum rate.
251: *
252: * @param limit the number of records allowed to be under the rate.
253: * @param millis the current time in milliseconds.
254: * @return true if not saturated or bellow the rate.
255: */
256: private boolean test(final long limit, final long millis) {
257:• assert limit >= 0L : limit;
258: final long c;
259: final long s;
260: synchronized (this) {
261: c = count;
262: s = start;
263: }
264:
265:• if (c > 0L) { //If not saturated.
266:• if ((millis - s) >= duration || c < limit) {
267: return true;
268: }
269: } else { //Subtraction is used to deal with numeric overflow.
270:• if ((millis - s) >= 0L || c == 0L) {
271: return true;
272: }
273: }
274: return false;
275: }
276:
277: /**
278: * Determines if the record is loggable by time.
279: *
280: * @param millis the log record milliseconds.
281: * @return true if accepted false otherwise.
282: */
283: private synchronized boolean accept(final long millis) {
284: //Subtraction is used to deal with numeric overflow of millis.
285: boolean allow;
286:• if (count > 0L) { //If not saturated.
287:• if ((millis - peak) > 0L) {
288: peak = millis; //Record the new peak.
289: }
290:
291: //Under the rate if the count has not been reached.
292:• if (count != records) {
293: ++count;
294: allow = true;
295: } else {
296:• if ((peak - start) >= duration) {
297: count = 1L; //Start a new duration.
298: start = peak;
299: allow = true;
300: } else {
301: count = -1L; //Saturate for the duration.
302: start = peak + duration;
303: allow = false;
304: }
305: }
306: } else {
307: //If the saturation period has expired or this is the first record
308: //then start a new duration and allow records.
309:• if ((millis - start) >= 0L || count == 0L) {
310: count = 1L;
311: start = millis;
312: peak = millis;
313: allow = true;
314: } else {
315: allow = false; //Remain in a saturated state.
316: }
317: }
318: return allow;
319: }
320:
321: /**
322: * Reads a long value or multiplication expression from the LogManager. If
323: * the value can not be parsed or is not defined then Long.MIN_VALUE is
324: * returned.
325: *
326: * @param suffix a dot character followed by the key name.
327: * @return a long value or Long.MIN_VALUE if unable to parse or undefined.
328: * @throws NullPointerException if suffix is null.
329: */
330: private long initLong(final String suffix) {
331: long result = 0L;
332: final String p = getClass().getName();
333: String value = fromLogManager(p.concat(suffix));
334:• if (value != null && value.length() != 0) {
335: value = value.trim();
336:• if (isTimeEntry(suffix, value)) {
337: try {
338: result = LogManagerProperties.parseDurationToMillis(value);
339: } catch (final RuntimeException ignore) {
340: } catch (final Exception ignore) {
341: } catch (final LinkageError ignore) {
342: }
343: }
344:
345:• if (result == 0L) { //Zero is invalid.
346: try {
347: result = 1L;
348:• for (String s : tokenizeLongs(value)) {
349:• if (s.endsWith("L") || s.endsWith("l")) {
350: s = s.substring(0, s.length() - 1);
351: }
352: result = multiplyExact(result, Long.parseLong(s));
353: }
354: } catch (final RuntimeException ignore) {
355: result = Long.MIN_VALUE;
356: }
357: }
358: } else {
359: result = Long.MIN_VALUE;
360: }
361: return result;
362: }
363:
364: /**
365: * Determines if the given suffix can be a time unit and the value is
366: * encoded as an ISO ISO-8601 duration format.
367: *
368: * @param suffix the suffix property.
369: * @param value the value of the property.
370: * @return true if the entry is a time entry.
371: * @throws IndexOutOfBoundsException if value is empty.
372: * @throws NullPointerException if either argument is null.
373: */
374: private boolean isTimeEntry(final String suffix, final String value) {
375:• return (value.charAt(0) == 'P' || value.charAt(0) == 'p')
376:• && suffix.equals(".duration");
377: }
378:
379: /**
380: * Parse any long value or multiplication expressions into tokens.
381: *
382: * @param value the expression or value.
383: * @return an array of long tokens, never empty.
384: * @throws NullPointerException if the given value is null.
385: * @throws NumberFormatException if the expression is invalid.
386: */
387: private static String[] tokenizeLongs(final String value) {
388: String[] e;
389: final int i = value.indexOf('*');
390:• if (i > -1 && (e = value.split("\\s*\\*\\s*")).length != 0) {
391:• if (i == 0 || value.charAt(value.length() - 1) == '*') {
392: throw new NumberFormatException(value);
393: }
394:
395:• if (e.length == 1) {
396: throw new NumberFormatException(e[0]);
397: }
398: } else {
399: e = new String[]{value};
400: }
401: return e;
402: }
403:
404: /**
405: * Multiply and check for overflow. This can be replaced with
406: * {@code java.lang.Math.multiplyExact} when Jakarta Mail requires JDK 8.
407: *
408: * @param x the first value.
409: * @param y the second value.
410: * @return x times y.
411: * @throws ArithmeticException if overflow is detected.
412: */
413: private static long multiplyExact(final long x, final long y) {
414: long r = x * y;
415:• if (((Math.abs(x) | Math.abs(y)) >>> 31L != 0L)) {
416:• if (((y != 0L) && (r / y != x))
417: || (x == Long.MIN_VALUE && y == -1L)) {
418: throw new ArithmeticException();
419: }
420: }
421: return r;
422: }
423:
424: /**
425: * Converts record count to a valid record count. If the value is out of
426: * bounds then the default record count is used.
427: *
428: * @param records the record count.
429: * @return a valid number of record count.
430: */
431: private static long checkRecords(final long records) {
432:• return records > 0L ? records : 1000L;
433: }
434:
435: /**
436: * Converts the duration to a valid duration. If the value is out of bounds
437: * then the default duration is used.
438: *
439: * @param duration the duration to check.
440: * @return a valid duration.
441: */
442: private static long checkDuration(final long duration) {
443:• return duration > 0L ? duration : 15L * 60L * 1000L;
444: }
445: }