2727 * ConcurrentHashMap to support concurrent class loading operations.</p>
2828 */
2929public class MavenNexusClassSource implements ClassSource {
30+ private static final long MAX_JAR_SIZE = 100 * 1024 * 1024 ; // 100MB default max JAR size
31+ private static final long MAX_CLASS_SIZE = 10 * 1024 * 1024 ; // 10MB default max class size
32+ private static final int DEFAULT_CONNECT_TIMEOUT = 10000 ; // 10 seconds
33+ private static final int DEFAULT_READ_TIMEOUT = 30000 ; // 30 seconds
34+
3035 private final String nexusUrl ;
3136 private final String repository ;
3237 private final List <MavenArtifact > artifacts ;
3338 private final AuthConfig authConfig ;
3439 private final Map <String , byte []> classCache ;
40+ private final int connectTimeout ;
41+ private final int readTimeout ;
3542
3643 /**
37- * Creates a Maven Nexus class source with full configuration.
44+ * Creates a Maven Nexus class source with full configuration including timeouts .
3845 *
3946 * @param nexusUrl The Nexus server URL
4047 * @param repository The Maven repository name
4148 * @param artifacts The list of Maven artifacts to load classes from
4249 * @param authConfig The authentication configuration
50+ * @param connectTimeout Connection timeout in milliseconds
51+ * @param readTimeout Read timeout in milliseconds
4352 * @throws NullPointerException if nexusUrl, repository, or artifacts is null
44- * @throws IllegalArgumentException if artifacts list is empty
53+ * @throws IllegalArgumentException if artifacts list is empty or timeouts are negative
4554 */
46- public MavenNexusClassSource (String nexusUrl , String repository , List <MavenArtifact > artifacts , AuthConfig authConfig ) {
55+ public MavenNexusClassSource (String nexusUrl , String repository , List <MavenArtifact > artifacts ,
56+ AuthConfig authConfig , int connectTimeout , int readTimeout ) {
4757 Objects .requireNonNull (nexusUrl , "nexusUrl cannot be null" );
4858 Objects .requireNonNull (artifacts , "artifacts cannot be null" );
4959
5060 if (artifacts .isEmpty ()) {
5161 throw new IllegalArgumentException ("At least one Maven artifact must be specified" );
5262 }
63+ if (connectTimeout < 0 ) {
64+ throw new IllegalArgumentException ("connectTimeout must be >= 0" );
65+ }
66+ if (readTimeout < 0 ) {
67+ throw new IllegalArgumentException ("readTimeout must be >= 0" );
68+ }
5369
5470 this .nexusUrl = nexusUrl .endsWith ("/" ) ? nexusUrl : nexusUrl + "/" ;
5571 this .repository = Objects .requireNonNull (repository , "repository cannot be null" );
5672 this .artifacts = new ArrayList <>(artifacts );
5773 this .authConfig = authConfig != null ? authConfig : AuthConfig .none ();
5874 this .classCache = new ConcurrentHashMap <>();
75+ this .connectTimeout = connectTimeout ;
76+ this .readTimeout = readTimeout ;
77+ }
78+
79+ /**
80+ * Creates a Maven Nexus class source with default timeouts.
81+ *
82+ * @param nexusUrl The Nexus server URL
83+ * @param repository The Maven repository name
84+ * @param artifacts The list of Maven artifacts to load classes from
85+ * @param authConfig The authentication configuration
86+ */
87+ public MavenNexusClassSource (String nexusUrl , String repository , List <MavenArtifact > artifacts , AuthConfig authConfig ) {
88+ this (nexusUrl , repository , artifacts , authConfig , DEFAULT_CONNECT_TIMEOUT , DEFAULT_READ_TIMEOUT );
5989 }
6090
6191 /**
@@ -79,6 +109,7 @@ public byte[] loadClassData(String className) throws IOException {
79109 }
80110
81111 String classFileName = ClassNameUtil .toClassFilePath (className );
112+ List <String > errorMessages = new ArrayList <>();
82113
83114 for (MavenArtifact artifact : artifacts ) {
84115 try {
@@ -87,10 +118,19 @@ public byte[] loadClassData(String className) throws IOException {
87118 classCache .put (cacheKey , classData );
88119 return classData ;
89120 } catch (IOException e ) {
121+ // Accumulate errors instead of silently swallowing
122+ String errorMsg = String .format ("Artifact %s - %s" ,
123+ artifact .toString (), e .getMessage ());
124+ errorMessages .add (errorMsg );
90125 }
91126 }
92127
93- throw new IOException ("Class not found in any configured Maven artifacts: " + className );
128+ // Throw with ALL error details
129+ String allErrors = String .join ("\n - " , errorMessages );
130+ throw new IOException (
131+ "Class not found in any of " + artifacts .size () + " configured Maven artifacts: " +
132+ className + "\n Attempted artifacts:\n - " + allErrors
133+ );
94134 }
95135
96136 @ Override
@@ -116,12 +156,22 @@ private String buildJarUrl(MavenArtifact artifact) {
116156 private byte [] extractClassFromJar (String jarUrl , String classFileName ) throws IOException {
117157 URL url = new URL (jarUrl );
118158 HttpURLConnection connection = (HttpURLConnection ) url .openConnection ();
159+ connection .setConnectTimeout (connectTimeout );
160+ connection .setReadTimeout (readTimeout );
119161 configureAuthentication (connection );
120162 connection .setRequestMethod ("GET" );
121163
122164 int responseCode = connection .getResponseCode ();
123165 if (responseCode != HttpURLConnection .HTTP_OK ) {
124- throw new IOException ("HTTP error code: " + responseCode + " for JAR URL: " + jarUrl );
166+ throw new IOException ("HTTP " + responseCode + " for JAR: " + jarUrl );
167+ }
168+
169+ // Check JAR size before downloading
170+ long contentLength = connection .getContentLengthLong ();
171+ if (contentLength > MAX_JAR_SIZE ) {
172+ throw new IOException (
173+ "JAR too large: " + contentLength + " bytes (max " + MAX_JAR_SIZE + ")"
174+ );
125175 }
126176
127177 try (InputStream in = connection .getInputStream ();
@@ -130,18 +180,34 @@ private byte[] extractClassFromJar(String jarUrl, String classFileName) throws I
130180 JarEntry entry ;
131181 while ((entry = jarIn .getNextJarEntry ()) != null ) {
132182 if (entry .getName ().equals (classFileName ) && !entry .isDirectory ()) {
183+ long size = entry .getSize ();
184+
185+ if (size > MAX_CLASS_SIZE ) {
186+ throw new IOException (
187+ "Class too large: " + size + " bytes (max " + MAX_CLASS_SIZE + ")"
188+ );
189+ }
190+
191+ // Read with size enforcement
133192 ByteArrayOutputStream out = new ByteArrayOutputStream ();
134193 byte [] buffer = new byte [DEFAULT_BUFFER_SIZE ];
194+ long totalRead = 0 ;
135195 int bytesRead ;
196+
136197 while ((bytesRead = jarIn .read (buffer )) != -1 ) {
198+ totalRead += bytesRead ;
199+ if (totalRead > MAX_CLASS_SIZE ) {
200+ throw new IOException ("Class exceeded size limit: " + totalRead );
201+ }
137202 out .write (buffer , 0 , bytesRead );
138203 }
204+
139205 return out .toByteArray ();
140206 }
141207 }
142208 }
143209
144- throw new IOException ("Class file not found in JAR: " + classFileName );
210+ throw new IOException ("Class not found in JAR: " + classFileName + " (URL: " + jarUrl + ")" );
145211 }
146212
147213 private void configureAuthentication (HttpURLConnection connection ) {
@@ -182,6 +248,8 @@ public static class Builder {
182248 private String repository ;
183249 private final List <MavenArtifact > artifacts = new ArrayList <>();
184250 private AuthConfig authConfig = AuthConfig .none ();
251+ private int connectTimeout = DEFAULT_CONNECT_TIMEOUT ;
252+ private int readTimeout = DEFAULT_READ_TIMEOUT ;
185253
186254 public Builder nexusUrl (String nexusUrl ) {
187255 this .nexusUrl = Objects .requireNonNull (nexusUrl , "nexusUrl cannot be null" );
@@ -211,10 +279,27 @@ public Builder auth(AuthConfig authConfig) {
211279 return this ;
212280 }
213281
282+ public Builder connectTimeout (int timeoutMs ) {
283+ if (timeoutMs < 0 ) {
284+ throw new IllegalArgumentException ("connectTimeout must be >= 0" );
285+ }
286+ this .connectTimeout = timeoutMs ;
287+ return this ;
288+ }
289+
290+ public Builder readTimeout (int timeoutMs ) {
291+ if (timeoutMs < 0 ) {
292+ throw new IllegalArgumentException ("readTimeout must be >= 0" );
293+ }
294+ this .readTimeout = timeoutMs ;
295+ return this ;
296+ }
297+
214298 public MavenNexusClassSource build () {
215299 Objects .requireNonNull (nexusUrl , "nexusUrl must be set" );
216300 Objects .requireNonNull (repository , "repository must be set" );
217- return new MavenNexusClassSource (nexusUrl , repository , artifacts , authConfig );
301+ return new MavenNexusClassSource (nexusUrl , repository , artifacts , authConfig ,
302+ connectTimeout , readTimeout );
218303 }
219304 }
220305}
0 commit comments