diff --git a/build.gradle b/build.gradle index e6afa0c..15e5cf8 100644 --- a/build.gradle +++ b/build.gradle @@ -53,6 +53,8 @@ dependencies { //javers implementation 'org.javers:javers-core:7.2.0' + // xml jaxb + implementation 'jakarta.xml.bind:jakarta.xml.bind-api:+' } diff --git a/src/main/java/ybe/mini/travelserver/global/api/TourAPIService.java b/src/main/java/ybe/mini/travelserver/global/api/TourAPIService.java index 2c77188..966b5cc 100644 --- a/src/main/java/ybe/mini/travelserver/global/api/TourAPIService.java +++ b/src/main/java/ybe/mini/travelserver/global/api/TourAPIService.java @@ -26,7 +26,7 @@ public Accommodation bringAccommodation( var body = accommodationsSimpleSearchResponse.response().body(); - if (body.totalCount() == 0) { + if (body.numOfRows() == 0) { throw new NoAccommodationsFromAPIException(); } @@ -59,7 +59,7 @@ public List bringAccommodations( var body = accommodationsSimpleSearchResponse.response().body(); - if (body.totalCount() == 0) { + if (body.numOfRows() == 0) { throw new NoAccommodationsFromAPIException(); } var items = body.items().item(); @@ -103,7 +103,7 @@ public List bringRooms(long accommodationId) { var body = roomTourAPIResponse.response().body(); - if (body.totalCount() == 0) { + if (body.numOfRows() == 0) { throw new NoRoomsFromAPIException(); } diff --git a/src/main/java/ybe/mini/travelserver/global/api/TourAPIUtils.java b/src/main/java/ybe/mini/travelserver/global/api/TourAPIUtils.java index 882d4e4..6db0658 100644 --- a/src/main/java/ybe/mini/travelserver/global/api/TourAPIUtils.java +++ b/src/main/java/ybe/mini/travelserver/global/api/TourAPIUtils.java @@ -1,5 +1,10 @@ package ybe.mini.travelserver.global.api; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.JAXBException; +import jakarta.xml.bind.Unmarshaller; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -7,16 +12,18 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; -import org.springframework.util.StreamUtils; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.client.HttpMessageConverterExtractor; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.DefaultUriBuilderFactory; import ybe.mini.travelserver.global.api.dto.AccommodationTourAPIResponse; import ybe.mini.travelserver.global.api.dto.RoomTourAPIResponse; -import ybe.mini.travelserver.global.exception.api.WrongCallBackException; +import ybe.mini.travelserver.global.exception.api.TourAPIXMLErrorResponse; +import ybe.mini.travelserver.global.exception.api.WrongRequestException; +import ybe.mini.travelserver.global.exception.api.WrongXMLFormatException; import java.net.URLEncoder; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Objects; @@ -31,6 +38,14 @@ public class TourAPIUtils { DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(); factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE); restTemplate.setUriTemplateHandler(factory); + + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json() + .featuresToEnable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT) + .build(); + + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(objectMapper); + + restTemplate.getMessageConverters().add(0, converter); } private static StringBuilder buildCommonUrl(String endpoint) { @@ -63,9 +78,17 @@ private static T fetchDataFromAPI( clientHttpResponse -> { MediaType contentType = clientHttpResponse.getHeaders().getContentType(); if (contentType != null && contentType.includes(MediaType.TEXT_XML)) { - String body = StreamUtils.copyToString(clientHttpResponse.getBody(), Charset.defaultCharset()); - log.error("공공포텅 오류 XML 반환 : {}", body); - throw new WrongCallBackException(); + try { + JAXBContext jaxbContext = JAXBContext.newInstance(TourAPIXMLErrorResponse.class); + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + TourAPIXMLErrorResponse tourAPIXMLErrorResponse = (TourAPIXMLErrorResponse) unmarshaller.unmarshal(clientHttpResponse.getBody()); + log.error("공공포털 오류 XML 반환 : {}", tourAPIXMLErrorResponse); + + String errorMessage = tourAPIXMLErrorResponse.getErrorHeader().getReturnAuthMsg(); + throw new WrongRequestException(errorMessage); + } catch (JAXBException e) { + throw new WrongXMLFormatException(e); + } } return new HttpMessageConverterExtractor<>(responseType, restTemplate.getMessageConverters()) diff --git a/src/main/java/ybe/mini/travelserver/global/exception/api/TourAPIErrorMessage.java b/src/main/java/ybe/mini/travelserver/global/exception/api/TourAPIErrorMessage.java index f7bd0bc..719e7c9 100644 --- a/src/main/java/ybe/mini/travelserver/global/exception/api/TourAPIErrorMessage.java +++ b/src/main/java/ybe/mini/travelserver/global/exception/api/TourAPIErrorMessage.java @@ -3,17 +3,38 @@ import lombok.AllArgsConstructor; import lombok.Getter; import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import ybe.mini.travelserver.global.exception.ErrorMessage; -import static org.springframework.http.HttpStatus.BAD_REQUEST; -import static org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE; +import static org.springframework.http.HttpStatus.*; @Getter @AllArgsConstructor public enum TourAPIErrorMessage implements ErrorMessage { - NO_ACCOMMODATIONS_FROM_API(BAD_REQUEST, "API로부터 숙소를 가져오지 못했습니다."), - NO_ROOMS_FROM_API(BAD_REQUEST, "API로부터 객실을 가져오지 못했습니다."), - WRONG_CALLBACK(SERVICE_UNAVAILABLE, "잘못된 콜백입니다."); + // Tour API 숙박, 객실 비즈니스 오류 + NO_ACCOMMODATIONS_FROM_API(NO_CONTENT, "숙소 결과가 반환된 것이 없습니다"), + NO_ROOMS_FROM_API(NO_CONTENT, "객실 결과가 반환된 것이 없습니다"), + + // Tour API Keyword 검색 부분 오류 + INVALID_REQUEST_PARAMETER_ERROR(BAD_REQUEST, "잘못된 요청 파라메터입니다."), + NO_MANDATORY_REQUEST_PARAMETERS_ERROR(BAD_REQUEST, "필수 요청 파라메터가 없습니다."), + TEMPORARILY_DISABLE_THE_SERVICEKEY_ERROR(FORBIDDEN, "일시적으로 사용할 수 없는 서비스 키입니다."), + UNSIGNED_CALL_ERROR(FORBIDDEN, "서명되지 않은 호출입니다."), + NODATA_ERROR(NOT_FOUND, "데이터가 없습니다."), + SERVICETIMEOUT_ERROR(GATEWAY_TIMEOUT, "서비스 연결 실패입니다."), + DB_ERROR(INTERNAL_SERVER_ERROR, "데이터베이스 에러입니다."), + + // Tour API 공통 오류 + APPLICATION_ERROR(INTERNAL_SERVER_ERROR, "Tour API 서버 어플리케이션 에러입니다"), + HTTP_ERROR(INTERNAL_SERVER_ERROR, "Tour API 서버 HTTP 에러입니다"), + NO_OPENAPI_SERVICE_ERROR(INTERNAL_SERVER_ERROR, "해당 Tour API 서비스가 없거나 폐기되었습니다"), + SERVICE_ACCESS_DENIED_ERROR(FORBIDDEN, "Tour API 서비스 접근이 거부되었습니다"), + LIMITED_NUMBER_OF_SERVICE_REQUESTS_EXCEEDS_ERROR(FORBIDDEN, "Tour API 서비스 요청 제한 횟수를 초과하였습니다"), + SERVICE_KEY_IS_NOT_REGISTERED_ERROR(FORBIDDEN, "등록되지 않은 Tour API 서비스 키입니다"), + DEADLINE_HAS_EXPIRED_ERROR(FORBIDDEN, "Tour API 서비스 활용 기간이 만료되었습니다"), + UNREGISTERED_IP_ERROR(FORBIDDEN, "등록되지 않은 Tour API 서비스 IP입니다"), + UNKNOWN_ERROR(INTERNAL_SERVER_ERROR, "Tour API 서버 알 수 없는 에러입니다"); + private final HttpStatus status; private final String message; } diff --git a/src/main/java/ybe/mini/travelserver/global/exception/api/TourAPIExceptionHandler.java b/src/main/java/ybe/mini/travelserver/global/exception/api/TourAPIExceptionHandler.java index edd9883..bcf23e6 100644 --- a/src/main/java/ybe/mini/travelserver/global/exception/api/TourAPIExceptionHandler.java +++ b/src/main/java/ybe/mini/travelserver/global/exception/api/TourAPIExceptionHandler.java @@ -1,11 +1,15 @@ package ybe.mini.travelserver.global.exception.api; import jakarta.servlet.http.HttpServletRequest; +import org.springframework.http.HttpStatus; import org.springframework.http.ProblemDetail; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import ybe.mini.travelserver.global.exception.ProblemDetailCreator; +import static ybe.mini.travelserver.global.exception.api.TourAPIErrorMessage.*; + + @RestControllerAdvice public class TourAPIExceptionHandler extends ProblemDetailCreator { protected TourAPIExceptionHandler() { @@ -14,12 +18,23 @@ protected TourAPIExceptionHandler() { @ExceptionHandler(NoAccommodationsFromAPIException.class) public ProblemDetail handleNotGatheredAccommodationsFromAPIException(HttpServletRequest request) { - return createProblemDetail(TourAPIErrorMessage.NO_ACCOMMODATIONS_FROM_API, request); + return createProblemDetail(NO_ACCOMMODATIONS_FROM_API, request); } @ExceptionHandler(NoRoomsFromAPIException.class) public ProblemDetail handleNotGatheredRoomsFromAPIException(HttpServletRequest request) { - return createProblemDetail(TourAPIErrorMessage.NO_ROOMS_FROM_API, request); + return createProblemDetail(NO_ROOMS_FROM_API, request); } + @ExceptionHandler(WrongXMLFormatException.class) + public ProblemDetail handleWrongXMLFormatException( + WrongXMLFormatException ex, + HttpServletRequest request) { + return createProblemDetail(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR.value(), request); + } + + @ExceptionHandler(WrongRequestException.class) + public ProblemDetail handleWrongCallBackException(WrongRequestException ex, HttpServletRequest request) { + return createProblemDetail(valueOf(ex.getMessage()), request); + } } \ No newline at end of file diff --git a/src/main/java/ybe/mini/travelserver/global/exception/api/TourAPIXMLErrorResponse.java b/src/main/java/ybe/mini/travelserver/global/exception/api/TourAPIXMLErrorResponse.java new file mode 100644 index 0000000..e42bedd --- /dev/null +++ b/src/main/java/ybe/mini/travelserver/global/exception/api/TourAPIXMLErrorResponse.java @@ -0,0 +1,27 @@ +package ybe.mini.travelserver.global.exception.api; + +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlRootElement; +import lombok.Getter; + +@Getter +@XmlRootElement(name = "OpenAPI_ServiceResponse") +public class TourAPIXMLErrorResponse { + + @XmlElement(name = "cmmMsgHeader") + private ErrorHeader errorHeader; + + @Getter + public static class ErrorHeader { + + @XmlElement(name = "errMsg") + private String errMsg; + + @XmlElement(name = "returnAuthMsg") + private String returnAuthMsg; + + @XmlElement(name = "returnReasonCode") + private String returnReasonCode; + + } +} \ No newline at end of file diff --git a/src/main/java/ybe/mini/travelserver/global/exception/api/WrongCallBackException.java b/src/main/java/ybe/mini/travelserver/global/exception/api/WrongCallBackException.java deleted file mode 100644 index 51d5512..0000000 --- a/src/main/java/ybe/mini/travelserver/global/exception/api/WrongCallBackException.java +++ /dev/null @@ -1,4 +0,0 @@ -package ybe.mini.travelserver.global.exception.api; - -public class WrongCallBackException extends RuntimeException { -} diff --git a/src/main/java/ybe/mini/travelserver/global/exception/api/WrongRequestException.java b/src/main/java/ybe/mini/travelserver/global/exception/api/WrongRequestException.java new file mode 100644 index 0000000..44632a6 --- /dev/null +++ b/src/main/java/ybe/mini/travelserver/global/exception/api/WrongRequestException.java @@ -0,0 +1,15 @@ +package ybe.mini.travelserver.global.exception.api; + +public class WrongRequestException extends RuntimeException { + + public final String msg; + + public WrongRequestException(String msg) { + this.msg = msg; + } + + @Override + public String getMessage() { + return msg; + } +} diff --git a/src/main/java/ybe/mini/travelserver/global/exception/api/WrongXMLFormatException.java b/src/main/java/ybe/mini/travelserver/global/exception/api/WrongXMLFormatException.java new file mode 100644 index 0000000..69ff6e8 --- /dev/null +++ b/src/main/java/ybe/mini/travelserver/global/exception/api/WrongXMLFormatException.java @@ -0,0 +1,9 @@ +package ybe.mini.travelserver.global.exception.api; + +import jakarta.xml.bind.JAXBException; + +public class WrongXMLFormatException extends RuntimeException { + public WrongXMLFormatException(JAXBException e) { + super(e); + } +}