Skip to content

Commit 42c3cfb

Browse files
authored
Merge pull request #58 from Jalen-Stephens/39-bug-handle-io-exception-in-c2patoolinvoker-for-non-ai-images
39 bug handle io exception in c2patoolinvoker for non ai images
2 parents b756ece + d7d7d34 commit 42c3cfb

File tree

6 files changed

+185
-29
lines changed

6 files changed

+185
-29
lines changed

citations.md

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3438,4 +3438,83 @@ Used AI to generate, correct, and integrate full Swagger/OpenAPI documentation a
34383438

34393439
> Portions of this commit were generated with assistance from OpenAI ChatGPT (GPT-5) on November 13, 2025. All AI-generated content was reviewed, verified, and finalized by the development team.
34403440
3441-
---
3441+
---
3442+
3443+
### **Commit / Ticket Reference**
3444+
3445+
* **Commit:** `[bug/doc] Fix C2PAToolInvoker Error and Repair Swagger UI (#39)`
3446+
* **Ticket:** `#39 — Handle IO Exception Error in C2paToolInvoker`
3447+
* **NOTE:** `Also repaired Swagger UI so that it behaves correctly`
3448+
* **Date:** November 19, 2025
3449+
* **Team Member:** Isaac Schmidt
3450+
3451+
---
3452+
3453+
### **AI Tool Information**
3454+
3455+
* **Tool Used:** OpenAI ChatGPT (GPT-5)
3456+
* **Access Method:** ChatGPT Web (.edu academic access)
3457+
* **Configuration:** Default model settings
3458+
* **Cost:** $0 (no paid API calls)
3459+
3460+
---
3461+
3462+
### **Purpose of AI Assistance**
3463+
3464+
Used AI to diagnose and patch key backend issues affecting C2PA tool invocation and Swagger UI functionality. Assistance included:
3465+
3466+
* Identifying the root cause of a `NullPointerException` in `C2paToolInvoker` and recommending a safe error-handling path that returns a clean “no C2PA data” response instead of storing erroneous error codes.
3467+
* Debugging and fixing Swagger UI authentication behavior, ensuring Bearer tokens are passed correctly and endpoints load via `/v3/api-docs` and `/swagger-ui/index.html`.
3468+
* Correcting misapplied annotations in `AuthController` (`@RequestBody` mix-up between Spring and Swagger) that caused JSON request bodies to deserialize into null fields.
3469+
* Verifying DTO definitions (`RegisterRequest`, `LoginRequest`, `RefreshRequest`) and advising explicit JSON property annotations where necessary.
3470+
* Walking through troubleshooting steps for Spring Security, confirming Swagger’s `Authorize` flow, and validating that uploads (`/api/images/upload`) correctly receive JWTs.
3471+
* Ensuring folder structure, config classes, and OpenAPI definitions (`OpenApiConfig`) were properly wired and not interfering with request handling.
3472+
3473+
---
3474+
3475+
### **Prompts / Interaction Summary**
3476+
3477+
* “Here’s the stack trace — why are email and password null during signup?”
3478+
* “Why is Swagger UI not able to authorize file uploads?”
3479+
* “Is my repository structure causing the Swagger issue?”
3480+
* “Why does C2paToolInvoker throw a NullPointerException when the image has no manifest?”
3481+
* “How do I fix @RequestBody so JSON actually binds to my DTO?”
3482+
* “Write the assistance section in a copy-and-paste .md format.”
3483+
3484+
---
3485+
3486+
### **Resulting Artifacts**
3487+
3488+
* Updated and corrected backend components:
3489+
* `C2paToolInvoker.java` logic for null-safe error handling.
3490+
* `AuthController.java` corrected to use Spring’s `@RequestBody`.
3491+
* `AuthProxyService.java` updated with proper null-safe `escape` logic and logging.
3492+
* `ImageController.java` verified for proper Swagger + Bearer token behavior.
3493+
* Configuration fixes:
3494+
* `OpenApiConfig.java` corrected (`@SecurityScheme`, controller scan path).
3495+
* Validation of existing `SecurityConfig.java` for Swagger compatibility.
3496+
* Swagger UI restored to full functionality:
3497+
* Correctly loads `/v3/api-docs`
3498+
* Accepts JWT via **Authorize**
3499+
* Allows image upload with bearer token
3500+
* Renders all secured endpoints normally
3501+
3502+
---
3503+
3504+
### **Verification**
3505+
3506+
* Rebuilt project using:
3507+
```bash
3508+
mvn clean spring-boot:run
3509+
```
3510+
* Confirmed:
3511+
* All /auth/* endpoints bind JSON correctly (no null DTO fields).
3512+
* /auth/signup successfully proxies to Supabase without internal 500s.
3513+
* C2PA analysis now returns a clean “no C2PA data” message when appropriate.
3514+
* Swagger UI loads configuration without 401 or “Failed to load remote configuration”.
3515+
* Bearer token added via Swagger’s Authorize correctly authenticates image uploads.
3516+
* Manual and log-based verification performed for C2PA execution paths and auth flow.
3517+
3518+
### **Attribution Statement**
3519+
3520+
Portions of this commit were generated with assistance from OpenAI ChatGPT (GPT-5) on November 19, 2025. All AI-generated recommendations and code were reviewed, tested, and validated by the development team prior to inclusion.

src/main/java/dev/coms4156/project/metadetect/c2pa/C2paToolInvoker.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
public class C2paToolInvoker {
1111

1212
private final String c2paToolPath;
13+
public static final String NO_C2PA_MANIFEST_MESSAGE =
14+
"This image does not contain C2PA data (no C2PA manifest found).";
1315

1416
public C2paToolInvoker(String c2paToolPath) {
1517
this.c2paToolPath = c2paToolPath;
@@ -43,11 +45,22 @@ public String extractManifest(File imageFile) throws IOException {
4345
String stderr = se.useDelimiter("\\A").hasNext() ? se.next() : "";
4446

4547
if (exit != 0) {
48+
// Special-case: images with *no* C2PA manifest. c2patool will
49+
// typically return a non-zero exit code with a "no manifest found"
50+
// message on stderr. We normalize that into a user-friendly message
51+
// so callers can distinguish "no C2PA data" from a hard failure.
52+
String lowerStderr = stderr == null ? "" : stderr.toLowerCase();
53+
if (!lowerStderr.isBlank() && lowerStderr.contains("no claim found")) {
54+
throw new IOException(NO_C2PA_MANIFEST_MESSAGE);
55+
}
56+
57+
// Any other non-zero exit is treated as an unexpected C2PA failure.
4658
String msg = "C2PA tool failed with exit code " + exit
4759
+ (stderr.isBlank() ? "" : " | stderr: " + stderr);
4860
throw new IOException(msg);
4961
}
5062
return stdout; // should already be JSON from -d
63+
5164
} catch (InterruptedException ie) {
5265
Thread.currentThread().interrupt();
5366
throw new IOException("C2PA tool execution was interrupted", ie);

src/main/java/dev/coms4156/project/metadetect/controller/AuthController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import dev.coms4156.project.metadetect.service.AuthProxyService;
55
import dev.coms4156.project.metadetect.service.UserService;
66
import io.swagger.v3.oas.annotations.Operation;
7-
import io.swagger.v3.oas.annotations.parameters.RequestBody;
87
import io.swagger.v3.oas.annotations.responses.ApiResponse;
98
import io.swagger.v3.oas.annotations.responses.ApiResponses;
109
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
@@ -16,6 +15,7 @@
1615
import org.springframework.web.bind.annotation.ExceptionHandler;
1716
import org.springframework.web.bind.annotation.GetMapping;
1817
import org.springframework.web.bind.annotation.PostMapping;
18+
import org.springframework.web.bind.annotation.RequestBody;
1919
import org.springframework.web.bind.annotation.RequestMapping;
2020
import org.springframework.web.bind.annotation.RestController;
2121

src/main/java/dev/coms4156/project/metadetect/dto/Dtos.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.coms4156.project.metadetect.dto;
22

3+
import com.fasterxml.jackson.annotation.JsonProperty;
34
import java.time.Instant;
45
import java.time.OffsetDateTime;
56
import java.util.List;
@@ -155,20 +156,32 @@ public record UpdateImageRequest(
155156
/**
156157
* Request body for registering a new user.
157158
*/
158-
public record RegisterRequest(String email, String password) { }
159+
public record RegisterRequest(
160+
@JsonProperty("email") String email,
161+
@JsonProperty("password") String password
162+
) { }
159163

160164
/**
161165
* Request body for logging in.
162166
*/
163-
public record LoginRequest(String email, String password) { }
167+
public record LoginRequest(
168+
@JsonProperty("email") String email,
169+
@JsonProperty("password") String password
170+
) { }
164171

165172
/**
166173
* Response returned after a successful login or registration.
167174
*/
168-
public record AuthResponse(String userId, String token) { }
175+
public record AuthResponse(
176+
@JsonProperty("userId") String userId,
177+
@JsonProperty("token") String token
178+
) { }
169179

170180
/**
171181
* Request body for obtaining a fresh access token.
172182
*/
173-
public record RefreshRequest(String refreshToken) { }
183+
public record RefreshRequest(
184+
@JsonProperty("refreshToken") String refreshToken
185+
) { }
186+
174187
}

src/main/java/dev/coms4156/project/metadetect/service/AnalyzeService.java

Lines changed: 63 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import dev.coms4156.project.metadetect.c2pa.C2paToolInvoker;
77
import dev.coms4156.project.metadetect.dto.Dtos;
88
import dev.coms4156.project.metadetect.model.AnalysisReport;
9+
import dev.coms4156.project.metadetect.model.AnalysisReport.ReportStatus;
910
import dev.coms4156.project.metadetect.model.Image;
1011
import dev.coms4156.project.metadetect.repository.AnalysisReportRepository;
1112
import dev.coms4156.project.metadetect.service.errors.MissingStoragePathException;
@@ -204,30 +205,44 @@ private void runExtractionAndFinalize(UUID analysisId, String storagePath) {
204205
// 2) Run C2PA extraction
205206
String manifestJson = c2paToolInvoker.extractManifest(tempFile);
206207

207-
// 3) Mark COMPLETED
208+
// 3) Mark COMPLETED (C2PA manifest present)
208209
markCompleted(analysisId, manifestJson, /*confidence*/ null);
209210

210-
} catch (Exception e) {
211-
// Capture a compact error message for the persisted details JSON
212-
String errMsg = truncate(e.toString(), 2000);
213-
214-
try {
215-
var errorObj = new java.util.LinkedHashMap<String, Object>();
216-
errorObj.put("error", errMsg);
217-
String errorJson = objectMapper.writeValueAsString(errorObj);
218-
markFailed(analysisId, errorJson);
219-
} catch (Exception jsonEx) {
220-
// Absolute fallback if JSON serialization fails
221-
markFailed(analysisId, "{\"error\":\"" + escapeForJson(errMsg) + "\"}");
222-
}
223-
} finally {
224-
// Best-effort cleanup of temp file
225-
if (tempFile != null) {
211+
} catch (IOException ioe) {
212+
// Special-case: image has NO C2PA manifest
213+
if (C2paToolInvoker.NO_C2PA_MANIFEST_MESSAGE.equals(ioe.getMessage())) {
226214
try {
227-
Files.deleteIfExists(tempFile.toPath());
228-
} catch (IOException ignored) {
229-
// Non-fatal during cleanup
215+
var noC2paObj = new java.util.LinkedHashMap<String, Object>();
216+
noC2paObj.put("message", "This image does not contain C2PA data");
217+
noC2paObj.put("hasC2pa", Boolean.FALSE);
218+
219+
String json = objectMapper.writeValueAsString(noC2paObj);
220+
221+
// Treat as a successfully completed analysis: no C2PA, but we can still
222+
// continue with AI image analysis downstream.
223+
markCompleted(analysisId, json, /*confidence*/ null);
224+
} catch (Exception jsonEx) {
225+
// If JSON building fails, still mark as COMPLETED with a simple message
226+
markCompleted(
227+
analysisId,
228+
"{\"message\":\"This image does not contain C2PA data\",\"hasC2pa\":false}",
229+
null
230+
);
230231
}
232+
} else {
233+
// Any other IOException is a real failure
234+
handleGenericFailure(analysisId, storagePath, ioe);
235+
}
236+
237+
} catch (Exception e) {
238+
// Non-IO exceptions: treat as failure as before
239+
handleGenericFailure(analysisId, storagePath, e);
240+
241+
} finally {
242+
// Best-effort cleanup of the temp file
243+
if (tempFile != null && tempFile.exists()) {
244+
//noinspection ResultOfMethodCallIgnored
245+
tempFile.delete();
231246
}
232247
}
233248
}
@@ -334,4 +349,32 @@ static AnalysisReport pending(UUID imageId, Instant createdAt) {
334349
return ar;
335350
}
336351
}
352+
353+
/**
354+
* Handles a generic failure during analysis by marking the analysis as FAILED
355+
* and storing the error message in the details field.
356+
*
357+
* @param analysisId id of the analysis to mark
358+
* @param storagePath storage path of the image being analyzed
359+
* @param e the exception that caused the failure
360+
*/
361+
362+
private void handleGenericFailure(UUID analysisId, String storagePath, Exception e) {
363+
String errMsg = truncate(e.toString(), 2000);
364+
365+
try {
366+
var errorObj = new java.util.LinkedHashMap<String, Object>();
367+
errorObj.put("error", errMsg);
368+
// optionally include more context:
369+
// errorObj.put("storagePath", storagePath);
370+
String errorJson = objectMapper.writeValueAsString(errorObj);
371+
markFailed(analysisId, errorJson);
372+
} catch (Exception jsonEx) {
373+
// absolute fallback (escape dangerous chars)
374+
markFailed(analysisId, "{\"error\":\"" + escapeForJson(errMsg) + "\"}");
375+
}
376+
}
377+
378+
379+
337380
}

src/main/java/dev/coms4156/project/metadetect/service/AuthProxyService.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package dev.coms4156.project.metadetect.service;
22

3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
35
import org.springframework.http.HttpStatusCode;
46
import org.springframework.http.MediaType;
57
import org.springframework.http.ResponseEntity;
@@ -23,6 +25,7 @@
2325
public class AuthProxyService {
2426

2527
private final WebClient supabase;
28+
private static final Logger log = LoggerFactory.getLogger(AuthProxyService.class);
2629

2730
/**
2831
* Constructs a proxy service using a preconfigured WebClient that already
@@ -43,6 +46,8 @@ public AuthProxyService(WebClient supabaseWebClient) {
4346
*/
4447
public ResponseEntity<String> signup(String email, String password) {
4548
String path = "/auth/v1/signup";
49+
50+
log.info("signup email={}, passwordNull={}", email, password == null);
4651
return forwardJson(
4752
path,
4853
"{\"email\":\"" + escape(email)
@@ -142,8 +147,11 @@ public String getBody() {
142147
* performs final validation.
143148
*/
144149
private static String escape(String s) {
145-
return s
146-
.replace("\\", "\\\\")
147-
.replace("\"", "\\\"");
150+
log.debug("escape called: input={}", s);
151+
if (s == null) {
152+
throw new IllegalArgumentException("value cannot be null");
153+
}
154+
return s.replace("\\", "\\\\")
155+
.replace("\"", "\\\"");
148156
}
149157
}

0 commit comments

Comments
 (0)