<aside> 💡

요약

01. 처리 흐름 (로직 요약)


static 데이터 ZIP 파일 다운로드

public File downloadHotelStatic(String downloadUrl) {
    return restTemplate.execute(
            downloadUrl,
            HttpMethod.GET,
            clientHttpRequest -> {
            },
            clientHttpResponse -> {
                File ret = File.createTempFile(
                    HikariConstant.HOTEL_RESPONSE_TEMP_FILE_PREFIX,
                    HikariConstant.HOTEL_RESPONSE_TEMP_FILE_SUFFIX
                );
                StreamUtils.copy(clientHttpResponse.getBody(),
                                 Files.newOutputStream(ret.toPath()));
                return ret;
            });
}

ZIP 압축 해제

public static File decompressAndDelete(File zipFile) {
    String decompressFilePath = zipFile.getAbsolutePath()
            .substring(0, zipFile.getAbsolutePath().lastIndexOf("."));

    File decompressFolder = new File(decompressFilePath);
    decompressFolder.mkdirs();

    try (FileInputStream fis = new FileInputStream(zipFile);
         ZipInputStream zis = new ZipInputStream(fis)) {

        ZipEntry zipEntry;
        while ((zipEntry = zis.getNextEntry()) != null) {
            File file = new File(decompressFilePath, zipEntry.getName());

            if (zipEntry.isDirectory()) {
                file.mkdirs();
            } else {
                createFile(file, zis);
            }
        }
    } finally {
        Files.delete(zipFile.toPath());
    }

    return decompressFolder;
}

압축 해제된 각 XML 파일을 문자열로 읽어서 JAXB로 파싱

for (File file : Objects.requireNonNull(decompressFile.listFiles())) {
    StringBuilder xmlBuilder = new StringBuilder();
    try (FileReader fileReader = new FileReader(file);
         BufferedReader bufferedReader = new BufferedReader(fileReader)) {

        String line;
        while ((line = bufferedReader.readLine()) != null) {
            xmlBuilder.append(line);
        }

        // 디버깅용: 앞 몇 글자 코드값 출력
        for (int i = 0; i < Math.min(5, xmlBuilder.length()); i++) {
            char c = xmlBuilder.toString().charAt(i);
            log.info("char[{}] = '{}' (code={})", i, c, (int) c);
        }

        HikariHotelStaticResponse hikariHotel =
                xmlUtils.unmarshalFromXml(xmlBuilder.toString(),
                                          HikariHotelStaticResponse.class);
        log.info("[Hikari] hotel static xml: {}", hikariHotel);
    }
}

JAXB 파싱 유틸

public <T> T unmarshalFromXml(String xml, Class<T> classOfT) throws JAXBException {
    return this.unmarshalFromXml(
            xml,
            classOfT,
            JAXBContext.newInstance(classOfT).createUnmarshaller()
    );
}

public <T> T unmarshalFromXml(String xml, Class<T> classOfT, Unmarshaller unmarshaller)
        throws JAXBException {
    return Primitives.wrap(classOfT)
                     .cast(unmarshaller.unmarshal(new StringReader(xml)));
}

02. 에러 이슈


로그 메시지 핵심

이 말은 XML 문서의 가장 첫 줄, 첫 번째 위치에서 XML로 인정할 수 없는 내용이 나왔다는 뜻이다. 정상적인 XML이라면 문서 시작은 보통 < 로 시작해야 한다.


03. 원인: XML 맨 앞에 포함된 BOM(Byte Order Mark)