001/* 002 * Copyright 2024-2026 Revetware LLC. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.soklet.servlet.jakarta; 018 019import jakarta.servlet.ReadListener; 020import jakarta.servlet.ServletInputStream; 021import org.jspecify.annotations.NonNull; 022 023import javax.annotation.concurrent.NotThreadSafe; 024import java.io.ByteArrayInputStream; 025import java.io.IOException; 026import java.io.InputStream; 027 028import static java.lang.String.format; 029import static java.util.Objects.requireNonNull; 030 031/** 032 * Soklet integration implementation of {@link ServletInputStream}. 033 * 034 * @author <a href="https://www.revetkn.com">Mark Allen</a> 035 */ 036@NotThreadSafe 037public final class SokletServletInputStream extends ServletInputStream { 038 @NonNull 039 private final InputStream inputStream; 040 @NonNull 041 private Boolean finished; 042 @NonNull 043 private Boolean closed; 044 045 @NonNull 046 public static SokletServletInputStream fromInputStream(@NonNull InputStream inputStream) { 047 requireNonNull(inputStream); 048 return new SokletServletInputStream(inputStream); 049 } 050 051 private SokletServletInputStream(@NonNull InputStream inputStream) { 052 super(); 053 requireNonNull(inputStream); 054 055 this.inputStream = inputStream; 056 this.finished = false; 057 this.closed = false; 058 059 if (inputStream instanceof ByteArrayInputStream && ((ByteArrayInputStream) inputStream).available() == 0) 060 this.finished = true; 061 } 062 063 @NonNull 064 private InputStream getInputStream() { 065 return this.inputStream; 066 } 067 068 @NonNull 069 private Boolean getFinished() { 070 return this.finished; 071 } 072 073 private void setFinished(@NonNull Boolean finished) { 074 requireNonNull(finished); 075 this.finished = finished; 076 } 077 078 @NonNull 079 private Boolean getClosed() { 080 return this.closed; 081 } 082 083 private void setClosed(@NonNull Boolean closed) { 084 requireNonNull(closed); 085 this.closed = closed; 086 } 087 088 private void ensureOpen() throws IOException { 089 if (getClosed()) 090 throw new IOException("Stream is closed"); 091 } 092 093 private void updateFinishedAfterRead(int bytesRead, 094 boolean bytesConsumed) { 095 if (bytesRead == -1) { 096 setFinished(true); 097 return; 098 } 099 100 if (bytesConsumed && getInputStream() instanceof ByteArrayInputStream 101 && ((ByteArrayInputStream) getInputStream()).available() == 0) { 102 setFinished(true); 103 } 104 } 105 106 // Implementation of ServletInputStream methods below: 107 108 @Override 109 public boolean isFinished() { 110 return getFinished(); 111 } 112 113 @Override 114 public boolean isReady() { 115 return !getClosed(); 116 } 117 118 @Override 119 public int available() throws IOException { 120 ensureOpen(); 121 return getInputStream().available(); 122 } 123 124 @Override 125 public void close() throws IOException { 126 if (getClosed()) 127 return; 128 129 try { 130 super.close(); 131 getInputStream().close(); 132 } finally { 133 setClosed(true); 134 setFinished(true); 135 } 136 } 137 138 @Override 139 public void setReadListener(@NonNull ReadListener readListener) { 140 requireNonNull(readListener); 141 throw new IllegalStateException(format("%s functionality is not supported", ReadListener.class.getSimpleName())); 142 } 143 144 @Override 145 public int read() throws IOException { 146 ensureOpen(); 147 int data = getInputStream().read(); 148 updateFinishedAfterRead(data, data != -1); 149 150 return data; 151 } 152 153 @Override 154 public int read(@NonNull byte[] b, 155 int off, 156 int len) throws IOException { 157 requireNonNull(b); 158 ensureOpen(); 159 int count = getInputStream().read(b, off, len); 160 updateFinishedAfterRead(count, count > 0); 161 162 return count; 163 } 164}