1 25 package org.archive.io; 26 27 import it.unimi.dsi.fastutil.io.FastBufferedOutputStream; 28 29 import java.io.FileOutputStream ; 30 import java.io.IOException ; 31 import java.io.OutputStream ; 32 import java.security.MessageDigest ; 33 import java.security.NoSuchAlgorithmException ; 34 35 import org.archive.util.IoUtils; 36 37 38 68 public class RecordingOutputStream extends OutputStream { 69 75 private long size = 0; 76 77 private String backingFilename; 78 private OutputStream diskStream = null; 79 80 86 private byte[] buffer; 87 88 89 private long position; 90 91 92 private boolean recording; 93 94 97 protected byte[] bufStreamBuf = 98 new byte [ FastBufferedOutputStream.DEFAULT_BUFFER_SIZE ]; 99 100 103 private boolean shouldDigest = false; 104 105 108 private MessageDigest digest = null; 109 110 113 private static final String SHA1 = "SHA1"; 114 115 120 protected static final long MAX_HEADER_MATERIAL = 1024*1024; 122 123 126 private long contentBeginMark; 127 128 131 private OutputStream out = null; 132 133 134 140 public RecordingOutputStream(int bufferSize, String backingFilename) { 141 this.buffer = new byte[bufferSize]; 142 this.backingFilename = backingFilename; 143 recording = true; 144 } 145 146 152 public void open() throws IOException { 153 this.open(null); 154 } 155 156 165 public void open(OutputStream wrappedStream) throws IOException { 166 if(isOpen()) { 167 throw new IOException ("ROS already open for " 170 +Thread.currentThread().getName()); 171 } 172 this.out = wrappedStream; 173 this.position = 0; 174 this.size = 0; 175 this.contentBeginMark = -1; 176 this.recording = true; 178 this.shouldDigest = false; 180 if (this.diskStream != null) { 181 closeDiskStream(); 182 } 183 if (this.diskStream == null) { 184 FileOutputStream fis = new FileOutputStream (this.backingFilename); 186 187 this.diskStream = new RecyclingFastBufferedOutputStream(fis, bufStreamBuf); 188 } 189 } 190 191 public void write(int b) throws IOException { 192 if(recording) { 193 record(b); 194 } 195 if (this.out != null) { 196 this.out.write(b); 197 } 198 checkLimits(); 199 } 200 201 public void write(byte[] b) throws IOException { 202 if(recording) { 203 record(b, 0, b.length); 204 } 205 if (this.out != null) { 206 this.out.write(b); 207 } 208 checkLimits(); 209 } 210 211 public void write(byte[] b, int off, int len) throws IOException { 212 if(recording) { 213 record(b, off, len); 214 } 215 if (this.out != null) { 216 this.out.write(b, off, len); 217 } 218 checkLimits(); 219 } 220 221 225 protected void checkLimits() throws RecorderTooMuchHeaderException { 226 if (contentBeginMark<0) { 227 if(position>MAX_HEADER_MATERIAL) { 229 throw new RecorderTooMuchHeaderException(); 230 } 231 } 232 } 233 234 241 private void record(int b) throws IOException { 242 if (this.shouldDigest) { 243 this.digest.update((byte)b); 244 } 245 if (this.position >= this.buffer.length) { 246 assert this.diskStream != null: "Diskstream is null"; 249 this.diskStream.write(b); 250 } else { 251 this.buffer[(int) this.position] = (byte) b; 252 } 253 this.position++; 254 } 255 256 265 private void record(byte[] b, int off, int len) throws IOException { 266 if(this.shouldDigest) { 267 assert this.digest != null: "Digest is null."; 268 this.digest.update(b, off, len); 269 } 270 tailRecord(b, off, len); 271 } 272 273 282 private void tailRecord(byte[] b, int off, int len) throws IOException { 283 if(this.position >= this.buffer.length){ 284 if (this.diskStream == null) { 287 throw new IOException ("diskstream is null"); 288 } 289 this.diskStream.write(b, off, len); 290 this.position += len; 291 } else { 292 assert this.buffer != null: "Buffer is null"; 293 int toCopy = (int)Math.min(this.buffer.length - this.position, len); 294 assert b != null: "Passed buffer is null"; 295 System.arraycopy(b, off, this.buffer, (int)this.position, toCopy); 296 this.position += toCopy; 297 if (toCopy < len) { 299 tailRecord(b, off + toCopy, len - toCopy); 300 } 301 } 302 } 303 304 public void close() throws IOException { 305 if(contentBeginMark<0) { 306 contentBeginMark = 0; 309 } 310 if (this.out != null) { 311 this.out.close(); 312 this.out = null; 313 } 314 closeRecorder(); 315 } 316 317 protected synchronized void closeDiskStream() 318 throws IOException { 319 if (this.diskStream != null) { 320 this.diskStream.close(); 321 this.diskStream = null; 322 } 323 } 324 325 public void closeRecorder() throws IOException { 326 recording = false; 327 closeDiskStream(); if (this.size == 0) { 331 this.size = this.position; 332 } 333 } 334 335 338 public void flush() throws IOException { 339 if (this.out != null) { 340 this.out.flush(); 341 } 342 if (this.diskStream != null) { 343 this.diskStream.flush(); 344 } 345 } 346 347 public ReplayInputStream getReplayInputStream() throws IOException { 348 return getReplayInputStream(0); 349 } 350 351 public ReplayInputStream getReplayInputStream(long skip) throws IOException { 352 assert this.out == null: "Stream is still open."; 356 ReplayInputStream replay = new ReplayInputStream(this.buffer, 357 this.size, this.contentBeginMark, this.backingFilename); 358 replay.skip(skip); 359 return replay; 360 } 361 362 368 public ReplayInputStream getContentReplayInputStream() throws IOException { 369 return getReplayInputStream(this.contentBeginMark); 370 } 371 372 public long getSize() { 373 return this.size; 374 } 375 376 381 public void markContentBegin() { 382 this.contentBeginMark = this.position; 383 startDigest(); 384 } 385 386 390 public void startDigest() { 391 if (this.digest != null) { 392 this.digest.reset(); 393 this.shouldDigest = true; 394 } 395 } 396 397 401 public void setSha1Digest() { 402 setDigest(SHA1); 403 } 404 405 406 414 public void setDigest(String algorithm) { 415 try { 416 if (this.digest == null || 418 !this.digest.getAlgorithm().equals(algorithm)) { 419 setDigest(MessageDigest.getInstance(algorithm)); 420 } 421 } catch (NoSuchAlgorithmException e) { 422 e.printStackTrace(); 423 } 424 } 425 426 435 public void setDigest(MessageDigest md) { 436 this.digest = md; 437 } 438 439 446 public byte[] getDigestValue() { 447 if(this.digest == null) { 448 return null; 449 } 450 return this.digest.digest(); 451 } 452 453 public ReplayCharSequence getReplayCharSequence() throws IOException { 454 return getReplayCharSequence(null); 455 } 456 457 public ReplayCharSequence getReplayCharSequence(String characterEncoding) 458 throws IOException { 459 return getReplayCharSequence(characterEncoding, this.contentBeginMark); 460 } 461 462 468 public ReplayCharSequence getReplayCharSequence(String characterEncoding, 469 long startOffset) throws IOException { 470 float maxBytesPerChar = IoUtils.encodingMaxBytesPerChar(characterEncoding); 472 if(maxBytesPerChar<=1) { 473 return new ByteReplayCharSequence( 476 this.buffer, 477 this.size, 478 startOffset, 479 this.backingFilename); 480 } else { 481 if(this.size <= this.buffer.length) { 483 return new MultiByteReplayCharSequence( 485 this.buffer, 486 this.size, 487 startOffset, 488 characterEncoding); 489 490 } else { 491 ReplayInputStream ris = getReplayInputStream(startOffset); 493 ReplayCharSequence rcs = new MultiByteReplayCharSequence( 494 ris, 495 this.backingFilename, 496 characterEncoding); 497 ris.close(); 498 return rcs; 499 } 500 501 } 502 503 } 504 505 public long getResponseContentLength() { 506 return this.size - this.contentBeginMark; 507 } 508 509 512 public boolean isOpen() { 513 return this.out != null; 514 } 515 } 516 | Popular Tags |