001/* 002 * Copyright 2018-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2018-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2018-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.util; 037 038 039 040import java.io.IOException; 041import java.io.OutputStream; 042 043 044 045/** 046 * This class provides an {@code OutputStream} implementation that uses a 047 * {@link FixedRateBarrier} to impose an upper bound on the rate (in bytes per 048 * second) at which data can be written to a wrapped {@code OutputStream}. 049 */ 050@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 051public final class RateLimitedOutputStream 052 extends OutputStream 053{ 054 // Indicates whether to automatically flush the stream after each write. 055 private final boolean autoFlush; 056 057 // The fixed-rate barrier that will serve as a rate limiter for this class. 058 @NotNull private final FixedRateBarrier rateLimiter; 059 060 // The output stream to which the data will actually be written. 061 @NotNull private final OutputStream wrappedStream; 062 063 // The maximum number of bytes that can be written in any single call to the 064 // rate limiter. 065 private final int maxBytesPerWrite; 066 067 068 069 /** 070 * Creates a new instance of this rate-limited output stream that wraps the 071 * provided output stream. 072 * 073 * @param wrappedStream The output stream to which the data will 074 * actually be written. It must not be 075 * {@code null}. 076 * @param maxBytesPerSecond The maximum number of bytes per second that can 077 * be written using this output stream. It must be 078 * greater than zero. 079 * @param autoFlush Indicates whether to automatically flush the 080 * wrapped output stream after each write. 081 */ 082 public RateLimitedOutputStream(@NotNull final OutputStream wrappedStream, 083 final int maxBytesPerSecond, 084 final boolean autoFlush) 085 { 086 Validator.ensureTrue((wrappedStream != null), 087 "RateLimitedOutputStream.wrappedStream must not be null."); 088 Validator.ensureTrue((maxBytesPerSecond > 0), 089 "RateLimitedOutputStream.maxBytesPerSecond must be greater than " + 090 "zero. The provided value was " + maxBytesPerSecond); 091 092 this.wrappedStream = wrappedStream; 093 this.autoFlush = autoFlush; 094 095 rateLimiter = new FixedRateBarrier(1000L, maxBytesPerSecond); 096 maxBytesPerWrite = Math.max(1, (maxBytesPerSecond / 100)); 097 } 098 099 100 101 /** 102 * Closes this output stream and the wrapped stream. 103 * 104 * @throws IOException If a problem is encountered while closing the wrapped 105 * output stream. 106 */ 107 @Override() 108 public void close() 109 throws IOException 110 { 111 wrappedStream.close(); 112 } 113 114 115 116 /** 117 * Writes a single byte of data to the wrapped output stream. 118 * 119 * @param b The byte of data to be written. Only the least significant 120 * eight bits will be written. 121 * 122 * @throws IOException If a problem is encountered while writing to the 123 * wrapped stream. 124 */ 125 @Override() 126 public void write(final int b) 127 throws IOException 128 { 129 rateLimiter.await(); 130 wrappedStream.write(b); 131 132 if (autoFlush) 133 { 134 wrappedStream.flush(); 135 } 136 } 137 138 139 140 /** 141 * Writes the contents of the provided array to the wrapped output stream. 142 * 143 * @param b The byte array containing the data to be written. It must not 144 * be {@code null}. 145 * 146 * @throws IOException If a problem is encountered while writing to the 147 * wrapped stream. 148 */ 149 @Override() 150 public void write(@NotNull final byte[] b) 151 throws IOException 152 { 153 write(b, 0, b.length); 154 } 155 156 157 158 /** 159 * Writes the contents of the specified portion of the provided array to the 160 * wrapped output stream. 161 * 162 * @param b The byte array containing the data to be written. It must 163 * not be {@code null}. 164 * @param offset The position in the provided array at which the data to 165 * write begins. It must be greater than or equal to zero and 166 * less than the length of the provided array. 167 * @param length The number of bytes to be written. It must not be 168 * negative, and the sum of offset and length must be less 169 * than or equal to the length of the provided array. 170 * 171 * @throws IOException If a problem is encountered while writing to the 172 * wrapped stream. 173 */ 174 @Override() 175 public void write(@NotNull final byte[] b, final int offset, final int length) 176 throws IOException 177 { 178 if (length <= 0) 179 { 180 return; 181 } 182 183 if (length <= maxBytesPerWrite) 184 { 185 rateLimiter.await(length); 186 wrappedStream.write(b, offset, length); 187 } 188 else 189 { 190 int pos = offset; 191 int remainingToWrite = length; 192 while (remainingToWrite > 0) 193 { 194 final int lengthThisWrite = 195 Math.min(remainingToWrite, maxBytesPerWrite); 196 rateLimiter.await(lengthThisWrite); 197 wrappedStream.write(b, pos, lengthThisWrite); 198 pos += lengthThisWrite; 199 remainingToWrite -= lengthThisWrite; 200 } 201 } 202 203 if (autoFlush) 204 { 205 wrappedStream.flush(); 206 } 207 } 208 209 210 211 /** 212 * Flushes the contents of the wrapped stream. 213 * 214 * @throws IOException If a problem is encountered while flushing the 215 * wrapped stream. 216 */ 217 @Override() 218 public void flush() 219 throws IOException 220 { 221 wrappedStream.flush(); 222 } 223}