Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,9 @@
## 2026-07-13 - 단일 패스 문자열 치환 최적화 (O(N) 단일 스캔 및 지연 할당)
**Learning:** `String.replace()`를 여러 번 체이닝하여 호출하면, 문자열 치환이 발생하지 않는 경우에도 내부적으로 불필요한 스캔이 중복 발생하고, 치환 시마다 새로운 문자열 객체와 char 배열이 할당되어 메모리 낭비와 성능 저하(GC 압박)가 발생한다.
**Action:** 여러 문자를 한 번에 치환해야 하는 경우, O(N) 단일 스캔을 통해 `charAt()`으로 문자를 확인하고, 치환이 실제로 필요한 경우에만 `StringBuilder`를 지연 할당(Lazy allocation)하여 성능을 최적화하고 불필요한 메모리 할당을 방지한다.
## 2026-07-20 - 문자열 치환 시 불필요한 객체 할당 방지를 위한 사전 검사(pre-scan) 적용
**Learning:** `String.replace("\u0000", "")` 와 같은 문자열 치환 메서드를 호출할 때, 대상 문자열에 치환할 문자가 없는 해피 패스(fast path)에서도 내부적으로 정규식 컴파일이나 불필요한 객체 할당 오버헤드가 발생할 수 있습니다.
**Action:** `String.replace`를 호출하기 전에 `indexOf()`를 사용하여 치환할 대상이 문자열에 실제로 존재하는지 먼저 확인(pre-scan)합니다. 대상 문자가 존재할 때만 `replace`를 호출하도록 하여 대부분의 정상적인 문자열 처리 시 발생하는 불필요한 메모리 할당 및 GC 압박을 방지해야 합니다.
## 2026-07-20 - 문자열 치환 시 불필요한 객체 할당 방지를 위한 사전 검사(pre-scan) 적용
**Learning:** `String.replace("\u0000", "")` 와 같은 문자열 치환 메서드를 호출할 때, 대상 문자열에 치환할 문자가 없는 해피 패스(fast path)에서도 내부적으로 정규식 컴파일이나 불필요한 객체 할당 오버헤드가 발생할 수 있습니다.
**Action:** `String.replace`를 호출하기 전에 `indexOf()`를 사용하여 치환할 대상이 문자열에 실제로 존재하는지 먼저 확인(pre-scan)합니다. 대상 문자가 존재할 때만 `replace`를 호출하도록 하여 대부분의 정상적인 문자열 처리 시 발생하는 불필요한 메모리 할당 및 GC 압박을 방지해야 합니다.
Comment on lines +22 to +27
12 changes: 5 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
## [Unreleased]
### Added
- **UI UX 개선**: 'Details' 버튼 클릭 시, 작업 상세 정보 로드 중에 사용자가 명시적인 로딩 상태를 확인할 수 있도록 'Loading...' 텍스트와 비활성화 상태를 표시하도록 추가했습니다.

# Changelog

## [Unreleased]

### 추가된 기능 (Added)
- **문자열 치환(String.replace) 성능 최적화**
- `indexOf("\u0000")` 검사를 통한 사전 필터링(pre-scan)을 추가하여, 치환 대상 문자가 없는 해피 패스(fast path)에서 발생하는 불필요한 객체 할당과 GC 오버헤드를 방지했습니다.
Comment on lines 1 to +5
- 적용 대상: `ConversionProperties`, `ArtifactStoreProperties`, `ConversionJob`, `ArtifactLinkService`, `TenantContext`, `TenantAccessService` 클래스의 문자열 정제(sanitize/clean) 메서드.
Comment on lines +4 to +6
- **UI UX 개선**: 'Details' 버튼 클릭 시, 작업 상세 정보 로드 중에 사용자가 명시적인 로딩 상태를 확인할 수 있도록 'Loading...' 텍스트와 비활성화 상태를 표시하도록 추가했습니다.
- **관리자용 단건 작업 삭제 및 재시도 API 추가**
- 특정 변환 작업을 삭제할 수 있는 `DELETE /api/v1/admin/convert/jobs/{jobId}` 엔드포인트를 추가했습니다.
- 실패(dead-lettered) 상태인 작업을 관리자가 재시도 큐에 등록할 수 있는 `POST /api/v1/admin/convert/jobs/{jobId}/retry` 엔드포인트를 추가했습니다.

- **비동기 버튼 로딩 피드백 및 상태 복원 개선**
- KPI 스냅샷 증거를 다시 불러오는 `refreshKpiEvidence` 동작 중에 "Refresh evidence" 버튼을 비활성화하고 "Refreshing..." 이라는 피드백을 제공하여 사용자의 중복 클릭을 방지했습니다.
- 버튼 상태 변경 시 내부 DOM 구조를 보존하기 위해 `Array.from(button.childNodes)`로 원래 노드를 저장하고, 성공 및 실패 후 `finally` 블록에서 `replaceChildren(...)`으로 안전하게 복원하도록 구현했습니다.

# Changelog

## [0.1.0] - 2026-06-25

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,11 @@ private static String nullableClean(String value) {
if (value == null) {
return null;
}
String cleaned = value.replace("\u0000", "").strip();
String cleaned = value;
if (cleaned.indexOf('\u0000') != -1) {
cleaned = cleaned.replace("\u0000", "");
}
cleaned = cleaned.strip();
Comment on lines +424 to +428
return cleaned.isEmpty() ? null : cleaned;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,11 @@ private static String clean(String value) {
if (value == null) {
return null;
}
String cleaned = value.replace("\u0000", "").strip();
String cleaned = value;
if (cleaned.indexOf('\u0000') != -1) {
cleaned = cleaned.replace("\u0000", "");
}
cleaned = cleaned.strip();
Comment on lines +161 to +165
return cleaned.isEmpty() ? null : cleaned;
}
}
8 changes: 5 additions & 3 deletions src/main/java/com/clearfolio/viewer/auth/TenantContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,11 @@ private static String sanitize(String value) {
return null;
}

String sanitized = value
.replace("\u0000", "")
.strip();
String sanitized = value;
if (sanitized.indexOf('\u0000') != -1) {
sanitized = sanitized.replace("\u0000", "");
}
sanitized = sanitized.strip();
Comment on lines +125 to +129
return sanitized.isEmpty() ? null : sanitized;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,15 @@ public String getRootDir() {
* @param rootDir artifact storage root directory
*/
public void setRootDir(String rootDir) {
String sanitized = rootDir == null ? "" : rootDir.replace("\u0000", "").strip();
if (rootDir == null) {
this.rootDir = DEFAULT_ROOT_DIR;
return;
}
String sanitized = rootDir;
if (sanitized.indexOf('\u0000') != -1) {
sanitized = sanitized.replace("\u0000", "");
}
sanitized = sanitized.strip();
this.rootDir = sanitized.isEmpty() ? DEFAULT_ROOT_DIR : sanitized;
}

Expand All @@ -79,6 +87,10 @@ private static String sanitize(String value) {
if (value == null) {
return "";
}
return value.replace("\u0000", "").strip().toLowerCase(Locale.ROOT);
String sanitized = value;
if (sanitized.indexOf('\u0000') != -1) {
sanitized = sanitized.replace("\u0000", "");
}
return sanitized.strip().toLowerCase(Locale.ROOT);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ public void setBlockedExtensions(Set<String> blockedExtensions) {
if (extension == null) {
continue;
}
String sanitized = extension
.replace("\u0000", "")
String sanitized = extension;
if (sanitized.indexOf('\u0000') != -1) {
sanitized = sanitized.replace("\u0000", "");
}
sanitized = sanitized
.trim()
.toLowerCase(Locale.ROOT);
if (!sanitized.isEmpty()) {
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/com/clearfolio/viewer/model/ConversionJob.java
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ private String sanitize(String value) {
if (value == null) {
return null;
}
return value.replace("\u0000", "");
if (value.indexOf('\u0000') != -1) {
return value.replace("\u0000", "");
}
return value;
Comment on lines +125 to +128
Comment on lines +125 to +128
}

private String normalizeOrDefault(String value, String fallback) {
Expand Down
Loading