diff --git a/pav-server/build.gradle b/pav-server/build.gradle index c7b529a3..21002060 100644 --- a/pav-server/build.gradle +++ b/pav-server/build.gradle @@ -44,6 +44,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-jdbc' developmentOnly 'org.springframework.boot:spring-boot-devtools' + implementation 'org.springframework.boot:spring-boot-starter-webflux' // thymeleaf implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' @@ -51,12 +52,12 @@ dependencies { // pdf create implementation 'com.itextpdf:html2pdf:5.0.1' - // db runtimeOnly 'mysql:mysql-connector-java' // log implementation 'ch.qos.logback:logback-classic' + // aws implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.0.1.RELEASE' implementation 'org.springframework.cloud:spring-cloud-aws-context:1.2.1.RELEASE' @@ -78,7 +79,7 @@ dependencies { // other implementation 'io.jsonwebtoken:jjwt:0.9.1' - implementation 'io.netty:netty-all:4.1.9.Final' +// implementation 'io.netty:netty-all:4.1.9.Final' implementation 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4:1.16' implementation 'org.apache.httpcomponents:httpclient:4.5.13' implementation 'org.apache.commons:commons-lang3:3.12.0' @@ -88,7 +89,7 @@ dependencies { implementation 'io.springfox:springfox-boot-starter:3.0.0' implementation 'org.json:json:20220320' - implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr353' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr353:2.15.2' // implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2' // implementation 'com.bedatadriven:jackson-datatype-jts:2.4' // implementation 'de.grundid.opendatalab:geojson-jackson:1.14' diff --git a/pav-server/src/main/java/com/palnet/biz/api/acnt/terms/controller/AcntTermsController.java b/pav-server/src/main/java/com/palnet/biz/api/acnt/terms/controller/AcntTermsController.java index 6706536d..f8654a95 100644 --- a/pav-server/src/main/java/com/palnet/biz/api/acnt/terms/controller/AcntTermsController.java +++ b/pav-server/src/main/java/com/palnet/biz/api/acnt/terms/controller/AcntTermsController.java @@ -10,7 +10,6 @@ import io.swagger.annotations.ApiOperation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -27,11 +26,8 @@ import java.util.List; @Tag(name = "약관 기본", description = "약관 관련 API") public class AcntTermsController { - @Autowired private final AcntTermsService service; - - - + @GetMapping(value = "/list") @Tag(name = "약관 기본", description = "약관 관련 API") @ApiOperation(value = "약관 기본 정보") diff --git a/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancAprvListRq.java b/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancAprvListRq.java index 64150c8c..41b66875 100644 --- a/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancAprvListRq.java +++ b/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancAprvListRq.java @@ -2,6 +2,7 @@ package com.palnet.biz.api.bas.laanc.model; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.palnet.biz.config.convert.InstantDateStrSerializer; +import io.swagger.annotations.ApiParam; import io.swagger.v3.oas.annotations.Parameter; import lombok.AllArgsConstructor; import lombok.Builder; @@ -27,18 +28,18 @@ import java.time.Instant; @AllArgsConstructor public class BasLaancAprvListRq { - @Parameter(name = "작성자 이름", example = "팔") + @ApiParam(name = "작성자 이름", example = "팔") private String memberName; - @Parameter(name = "등록시작일자", example = "2023-12-12") + @ApiParam(name = "등록시작일자", example = "2023-12-12") @JsonSerialize(using = InstantDateStrSerializer.class) private Instant createStDate; - @Parameter(name = "등록종료일자", example = "2023-12-12") + @ApiParam(name = "등록종료일자", example = "2023-12-12") @JsonSerialize(using = InstantDateStrSerializer.class) private Instant createEndDate; - @Parameter(name = "비행시작일자", example = "2023-12-12") + @ApiParam(name = "비행시작일자", example = "2023-12-12") @JsonSerialize(using = InstantDateStrSerializer.class) private Instant schFltStDate; - @Parameter(name = "비행종료일자", example = "2023-12-12") + @ApiParam(name = "비행종료일자", example = "2023-12-12") @JsonSerialize(using = InstantDateStrSerializer.class) private Instant schFltEndDate; diff --git a/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancAprvRs.java b/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancAprvRs.java index 7a071f65..4e4068c8 100644 --- a/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancAprvRs.java +++ b/pav-server/src/main/java/com/palnet/biz/api/bas/laanc/model/BasLaancAprvRs.java @@ -5,6 +5,7 @@ import com.palnet.biz.jpa.entity.type.ArcrftTypeCd; import com.palnet.biz.jpa.entity.type.ArcrftWghtCd; import com.palnet.biz.jpa.entity.type.FltPurpose; import com.palnet.biz.jpa.entity.type.FltType; +import io.swagger.annotations.ApiParam; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; @@ -33,54 +34,54 @@ import java.util.stream.Collectors; @NoArgsConstructor @AllArgsConstructor public class BasLaancAprvRs { - @Parameter(name = "비행계획서 일려번호") + @ApiParam(name = "비행계획서 일려번호") private Integer planSno; - @Parameter(name = "그룹 아이디", hidden = true) + @ApiParam(name = "그룹 아이디", hidden = true) private String groupId; - @Parameter(name = "작성자 일련번호") + @ApiParam(name = "작성자 일련번호") private Integer cstmrSno; - @Parameter(name = "작성자 이름") + @ApiParam(name = "작성자 이름") private String memberName; - @Parameter(name = "작성자 이메일") + @ApiParam(name = "작성자 이메일") private String email; - @Parameter(name = "작성자 연락처") + @ApiParam(name = "작성자 연락처") private String hpno; - @Parameter(name = "작성자 국가번호(연락처)", hidden = true) + @ApiParam(name = "작성자 국가번호(연락처)", hidden = true) private String clncd; - @Parameter(name = "작성자 주소", hidden = true) + @ApiParam(name = "작성자 주소", hidden = true) private String addr; - @Parameter(name = "작성자 상세주소", hidden = true) + @ApiParam(name = "작성자 상세주소", hidden = true) private String addrDtlCn; - @Parameter(name = "작성자 우편번호", hidden = true) + @ApiParam(name = "작성자 우편번호", hidden = true) private String zip; - @Parameter(name = "비행시작일시") + @ApiParam(name = "비행시작일시") private Instant schFltStDt; - @Parameter(name = "비행종료일시") + @ApiParam(name = "비행종료일시") private Instant schFltEndDt; - @Parameter(name = "비행목적") + @ApiParam(name = "비행목적") private FltPurpose fltPurpose; - @Parameter(name = "상업/비사업 구분") + @ApiParam(name = "상업/비사업 구분") private FltType fltType; - @Parameter(name = "승인여부") + @ApiParam(name = "승인여부") private String aprvlYn; - @Parameter(name = "승인일자") + @ApiParam(name = "승인일자") private Instant aprvlDt; // private String delYn; - @Parameter(name = "등록자ID") + @ApiParam(name = "등록자ID") private String createUserId; - @Parameter(name = "등록일시") + @ApiParam(name = "등록일시") private Instant createDt; - @Parameter(name = "수정자ID") + @ApiParam(name = "수정자ID") private String updateUserId; - @Parameter(name = "수정일시") + @ApiParam(name = "수정일시") private Instant updateDt; - @Parameter(name = "서비스유형") + @ApiParam(name = "서비스유형") private String serviceType; - @Parameter(name = "기업여부") + @ApiParam(name = "기업여부") private String corpRegYn; // 하나의 필드로 묶어서 처리 - @Parameter(name = "기체 무게") + @ApiParam(name = "기체 무게") public String getArcrftWght() { if (arcrftList == null || arcrftList.isEmpty()) return null; List arcrftWghtCdList = arcrftList.stream().map(BasLaancArcrftModel::getArcrftWghtCd).distinct().filter(Objects::nonNull).collect(Collectors.toList()); @@ -89,7 +90,7 @@ public class BasLaancAprvRs { return ArcrftWghtType; } - @Parameter(name = "고도") + @ApiParam(name = "고도") public String getElev() { if (areaList == null || areaList.isEmpty()) return null; List elevList = areaList.stream().map(BasLaancAreaModel::getFltElev).distinct().filter(Objects::nonNull).collect(Collectors.toList()); @@ -97,7 +98,7 @@ public class BasLaancAprvRs { return elev; } -// @Parameter(name = "조종사 성명") +// @ApiParam(name = "조종사 성명") // public String getPilotName() { // if (pilotList == null || pilotList.isEmpty()) return null; // List pilotNameList = pilotList.stream().map(BasLaancPilotModel::getMemberName).distinct().filter(Objects::nonNull).collect(Collectors.toList()); diff --git a/pav-server/src/main/java/com/palnet/biz/api/comn/sms/service/ComnSmsMapper.java b/pav-server/src/main/java/com/palnet/biz/api/comn/sms/service/ComnSmsMapper.java index 97ffe82d..ddaa13ce 100644 --- a/pav-server/src/main/java/com/palnet/biz/api/comn/sms/service/ComnSmsMapper.java +++ b/pav-server/src/main/java/com/palnet/biz/api/comn/sms/service/ComnSmsMapper.java @@ -22,6 +22,5 @@ import org.mapstruct.factory.Mappers; public interface ComnSmsMapper { ComnSmsMapper MAPPER = Mappers.getMapper(ComnSmsMapper.class); - SuredataEntity toEntity(ComnSmsLaancAprovModel model); } diff --git a/pav-server/src/main/java/com/palnet/biz/api/comn/sunriseset/controller/ComnSunrisesetController.java b/pav-server/src/main/java/com/palnet/biz/api/comn/sunriseset/controller/ComnSunrisesetController.java new file mode 100644 index 00000000..e4c55b03 --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/api/comn/sunriseset/controller/ComnSunrisesetController.java @@ -0,0 +1,69 @@ +package com.palnet.biz.api.comn.sunriseset.controller; + +import com.palnet.biz.api.comn.response.BasicResponse; +import com.palnet.biz.api.comn.response.ErrorResponse; +import com.palnet.biz.api.comn.response.SuccessResponse; +import com.palnet.biz.api.comn.sunriseset.model.ComnSunrisesetRq; +import com.palnet.biz.api.comn.sunriseset.model.ComnSunrisesetRs; +import com.palnet.biz.api.comn.sunriseset.service.ComnSunrisesetService; +import com.palnet.comn.exception.CustomException; +import io.swagger.annotations.ApiOperation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * packageName : com.palnet.biz.api.comn.sunriseset.controller + * fileName : ComnSunrisesetController + * author : dhji + * date : 2023-10-15(015) + * description : + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2023-10-15(015) dhji 최초 생성 + */ +@Slf4j +@Tag(name = "공통 API", description = "공통 API") +@RequiredArgsConstructor +@RequestMapping("/api/comn/sunriseset") +@RestController +public class ComnSunrisesetController { + + private final ComnSunrisesetService comnSunrisesetService; + + @ApiOperation(value = "LAANC 승인 상세 조회") + @Tag(name = "공통 API", description = "공통 API") + @GetMapping("/detail/{planSno}") + public ResponseEntity getSunRiseSetList(ComnSunrisesetRq rq) { + List rs = null; + try { + log.debug(">>> rq : {}", rq); + rs = comnSunrisesetService.getSunRiseSetList(rq); +// log.debug(">>> rs : {}", rs); + + } catch (CustomException e) { + Map resultMap = new HashMap(); + log.error("IGNORE : ", e); + resultMap.put("result", false); + resultMap.put("errorCode", e.getErrorCode()); + resultMap.put("errorMessage", e.getMessage()); + return ResponseEntity.ok().body(new SuccessResponse(resultMap)); + } catch (Exception e) { + log.error("IGNORE : ", e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new ErrorResponse("Server Error", "-1")); + + } + return ResponseEntity.ok().body(new SuccessResponse<>(rs)); + } +} diff --git a/pav-server/src/main/java/com/palnet/biz/api/comn/sunriseset/model/ComnSunrisesetRq.java b/pav-server/src/main/java/com/palnet/biz/api/comn/sunriseset/model/ComnSunrisesetRq.java new file mode 100644 index 00000000..777f176d --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/api/comn/sunriseset/model/ComnSunrisesetRq.java @@ -0,0 +1,33 @@ +package com.palnet.biz.api.comn.sunriseset.model; + +import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * packageName : com.palnet.biz.api.comn.sunriseset.model + * fileName : ComnSunrisesetRq + * author : dhji + * date : 2023-10-15(015) + * description : + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2023-10-15(015) dhji 최초 생성 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ComnSunrisesetRq { + @ApiParam(name = "시작일자", example = "20231101") + private String locStDate; + @ApiParam(name = "종료일자", example = "20231231") + private String locEndDate; + @ApiParam(name = "지역", example = "김포") + private String location; +} diff --git a/pav-server/src/main/java/com/palnet/biz/api/comn/sunriseset/model/ComnSunrisesetRs.java b/pav-server/src/main/java/com/palnet/biz/api/comn/sunriseset/model/ComnSunrisesetRs.java new file mode 100644 index 00000000..9746d1f5 --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/api/comn/sunriseset/model/ComnSunrisesetRs.java @@ -0,0 +1,40 @@ +package com.palnet.biz.api.comn.sunriseset.model; + +import io.swagger.annotations.ApiParam; +import io.swagger.v3.oas.annotations.Parameter; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; + +/** + * packageName : com.palnet.biz.api.comn.sunriseset.model + * fileName : ComnSunrisesetRs + * author : dhji + * date : 2023-10-15(015) + * description : + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2023-10-15(015) dhji 최초 생성 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ComnSunrisesetRs { + @ApiParam(name = "일자", example = "20231212") + private String locDate; + @ApiParam(name = "지역", example = "김포") + private String location; + @ApiParam(name = "일출", example = "071000") + private String sunrise; // 일출 + @ApiParam(name = "일몰", example = "183000") + private String sunset; // 일몰 + @ApiParam(name = "시민박명(아침)", example = "071000") + private String civilm; // 시민박명(아침) + @ApiParam(name = "시민박명(저녁)", example = "183000") + private String civile; // 시민박명(저녁) +} diff --git a/pav-server/src/main/java/com/palnet/biz/api/comn/sunriseset/service/ComnSunrisesetMapper.java b/pav-server/src/main/java/com/palnet/biz/api/comn/sunriseset/service/ComnSunrisesetMapper.java new file mode 100644 index 00000000..9ee897ce --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/api/comn/sunriseset/service/ComnSunrisesetMapper.java @@ -0,0 +1,33 @@ +package com.palnet.biz.api.comn.sunriseset.service; + +import com.palnet.biz.api.comn.sunriseset.model.ComnSunrisesetRs; +import com.palnet.biz.jpa.entity.ComRiseSetBas; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +/** + * packageName : com.palnet.biz.api.comn.sunriseset.service + * fileName : omnSunrisesetMapper + * author : dhji + * date : 2023-10-15(015) + * description : + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2023-10-15(015) dhji 최초 생성 + */ +@Mapper(unmappedSourcePolicy = ReportingPolicy.IGNORE, unmappedTargetPolicy = ReportingPolicy.IGNORE, nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface ComnSunrisesetMapper { + ComnSunrisesetMapper MAPPER = Mappers.getMapper(ComnSunrisesetMapper.class); + + @Mapping(target = "locDate", source = "id.locDate") + @Mapping(target = "location", source = "id.location") + ComnSunrisesetRs toRs(ComRiseSetBas entity); + + List toRs(List entites); +} diff --git a/pav-server/src/main/java/com/palnet/biz/api/comn/sunriseset/service/ComnSunrisesetService.java b/pav-server/src/main/java/com/palnet/biz/api/comn/sunriseset/service/ComnSunrisesetService.java new file mode 100644 index 00000000..ca2185ce --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/api/comn/sunriseset/service/ComnSunrisesetService.java @@ -0,0 +1,52 @@ +package com.palnet.biz.api.comn.sunriseset.service; + +import com.palnet.biz.api.comn.sunriseset.model.ComnSunrisesetRq; +import com.palnet.biz.api.comn.sunriseset.model.ComnSunrisesetRs; +import com.palnet.biz.jpa.entity.ComRiseSetBas; +import com.palnet.biz.jpa.repository.com.ComRiseSetBasRepository; +import com.palnet.biz.jpa.repository.com.ComRiseSetQueryRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StopWatch; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.List; + +/** + * packageName : com.palnet.biz.api.comn.sunriseset.service + * fileName : comnSunrisesetService + * author : dhji + * date : 2023-10-15(015) + * description : + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2023-10-15(015) dhji 최초 생성 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class ComnSunrisesetService { + + private final ComRiseSetQueryRepository comRiseSetQueryRepository; + + public List getSunRiseSetList(ComnSunrisesetRq rq) { + String locStDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); + String locEndDate = null; + if (rq != null) { + if (rq.getLocStDate() != null && !rq.getLocStDate().isEmpty()) { + locStDate = rq.getLocStDate(); + } + if (rq.getLocEndDate() != null && !rq.getLocEndDate().isEmpty()) { + locEndDate = rq.getLocEndDate(); + } + } + + List rs = comRiseSetQueryRepository.findAllBySearchTransform(rq); + + return rs; + + } +} diff --git a/pav-server/src/main/java/com/palnet/biz/api/external/model/SunRiseSetRq.java b/pav-server/src/main/java/com/palnet/biz/api/external/model/SunRiseSetRq.java new file mode 100644 index 00000000..1f5c7bd3 --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/api/external/model/SunRiseSetRq.java @@ -0,0 +1,15 @@ +package com.palnet.biz.api.external.model; + +/** + * packageName : com.palnet.biz.api.external.model + * fileName : SunRiseSetRq + * author : dhji + * date : 2023-10-12(012) + * description : + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2023-10-12(012) dhji 최초 생성 + */ +public class SunRiseSetRq { +} diff --git a/pav-server/src/main/java/com/palnet/biz/api/external/model/SunRiseSetRs.java b/pav-server/src/main/java/com/palnet/biz/api/external/model/SunRiseSetRs.java new file mode 100644 index 00000000..8cb9ea54 --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/api/external/model/SunRiseSetRs.java @@ -0,0 +1,93 @@ +package com.palnet.biz.api.external.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * packageName : com.palnet.biz.api.external.model + * fileName : SunRiseSetRs + * author : dhji + * date : 2023-10-12(012) + * description : + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2023-10-12(012) dhji 최초 생성 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@XmlRootElement(name = "item") +@XmlAccessorType(XmlAccessType.FIELD) +public class SunRiseSetRs { + + // 날짜 + @XmlElement(name = "locdate") + private String locdate; + // 지역 + @XmlElement(name = "location") + private String location; + + // 경도 + @XmlElement(name = "longitude") + private String longitude; + // 경도(실수) + @XmlElement(name = "longitudeNum") + private Double longitudeNum; + // 위도 + @XmlElement(name = "latitude") + private String latitude; + // 위도(실수) + @XmlElement(name = "latitudeNum") + private Double latitudeNum; + + // 일출 + @XmlElement(name = "sunrise") + private String sunrise; + // 일중 + @XmlElement(name = "suntransit") + private String suntransit; + // 일몰 + @XmlElement(name = "sunset") + private String sunset; + + // 월출 + @XmlElement(name = "moonrise") + private String moonrise; + // 월중 + @XmlElement(name = "moontransit") + private String moontransit; + // 월몰 + @XmlElement(name = "moonset") + private String moonset; + + // 시민박명(아침) + @XmlElement(name = "civilm") + private String civilm; + // 시민박명(저녁) + @XmlElement(name = "civile") + private String civile; + + // 향해박명(아침) + @XmlElement(name = "nautm") + private String nautm; + // 향해박명(저녁) + @XmlElement(name = "naute") + private String naute; + + // 천문박명(아침) + @XmlElement(name = "astm") + private String astm; + // 천문박명(저녁) + @XmlElement(name = "aste") + private String aste; + +} diff --git a/pav-server/src/main/java/com/palnet/biz/api/external/model/SunRiseSetXmlRs.java b/pav-server/src/main/java/com/palnet/biz/api/external/model/SunRiseSetXmlRs.java new file mode 100644 index 00000000..daa088ec --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/api/external/model/SunRiseSetXmlRs.java @@ -0,0 +1,75 @@ +package com.palnet.biz.api.external.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.*; +import java.util.List; + +/** + * packageName : com.palnet.biz.api.external.model + * fileName : SunRiseSetXmlRs + * author : dhji + * date : 2023-10-15(015) + * description : + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2023-10-15(015) dhji 최초 생성 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@XmlRootElement(name = "response") +@XmlAccessorType(XmlAccessType.FIELD) +public class SunRiseSetXmlRs { + + @XmlElement(name = "header") + private Header header; + @XmlElement(name = "body") + private Body body; + + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + @XmlRootElement(name = "header") + @XmlAccessorType(XmlAccessType.FIELD) + public static class Header { + // 결과 코드 + @XmlElement(name = "resultCode") + private String resultCode; + // 결과 메시지 + @XmlElement(name = "resultMsg") + private String resultMsg; + + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + @XmlRootElement(name = "body") + @XmlAccessorType(XmlAccessType.FIELD) + public static class Body { + // 모든 항목수 + @XmlElement(name = "totalCount") + private int totalCount; + + // 페이지 + @XmlElement(name = "pageNo") + private int pageNo; + + // 페이지당 항목수 + @XmlElement(name = "numOfRows") + private int numOfRows; + + @XmlElementWrapper(name = "items") + @XmlElement(name = "item") + private List items; + + } +} diff --git a/pav-server/src/main/java/com/palnet/biz/api/external/service/SunRiseSetMapper.java b/pav-server/src/main/java/com/palnet/biz/api/external/service/SunRiseSetMapper.java new file mode 100644 index 00000000..2de9b901 --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/api/external/service/SunRiseSetMapper.java @@ -0,0 +1,33 @@ +package com.palnet.biz.api.external.service; + +import com.palnet.biz.api.external.model.SunRiseSetRs; +import com.palnet.biz.jpa.entity.ComRiseSetBas; +import com.palnet.biz.jpa.entity.ComRiseSetBasPK; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * packageName : com.palnet.biz.api.external.service + * fileName : SunRiseSetMapper + * author : dhji + * date : 2023-10-15(015) + * description : + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2023-10-15(015) dhji 최초 생성 + */ +@Mapper(unmappedSourcePolicy = ReportingPolicy.IGNORE, unmappedTargetPolicy = ReportingPolicy.IGNORE, nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface SunRiseSetMapper { + SunRiseSetMapper MAPPER = Mappers.getMapper(SunRiseSetMapper.class); + + @Mapping(target = "lon", source="longitudeNum") + @Mapping(target = "lat", source="latitudeNum") + ComRiseSetBas toEntity(SunRiseSetRs rs); + + @Mapping(target = "locDate", source = "locdate") + ComRiseSetBasPK toEntityPk(SunRiseSetRs rs); +} diff --git a/pav-server/src/main/java/com/palnet/biz/api/external/service/SunRiseSetService.java b/pav-server/src/main/java/com/palnet/biz/api/external/service/SunRiseSetService.java new file mode 100644 index 00000000..d003cf5f --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/api/external/service/SunRiseSetService.java @@ -0,0 +1,168 @@ +package com.palnet.biz.api.external.service; + +import com.palnet.biz.api.external.model.SunRiseSetRs; +import com.palnet.biz.api.external.model.SunRiseSetXmlRs; +import com.palnet.biz.jpa.entity.ComRiseSetBas; +import com.palnet.biz.jpa.entity.ComRiseSetBasPK; +import com.palnet.biz.jpa.repository.com.ComRiseSetBasRepository; +import com.palnet.comn.utils.InstantUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.util.UriComponentsBuilder; + +import java.lang.reflect.Field; +import java.time.Instant; +import java.time.LocalDate; +import java.time.Period; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * packageName : com.palnet.biz.api.external.service + * fileName : SunRiseSetService + * author : dhji + * date : 2023-10-12(012) + * description : + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2023-10-12(012) dhji 최초 생성 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class SunRiseSetService { + + private final ComRiseSetBasRepository comRiseSetBasRepository; + + @Value("${external.sunriseset.url}") + private String SUN_HOST; + @Value("${external.sunriseset.key}") + private String SUN_KEY; + private final String AREA_RISE_SEET_INFO_URL = "/getAreaRiseSetInfo"; + // TODO 추후 특정 위치가 필요할 경우 활성화 + // private final String LOCATION_RISE_SEET_INFO_URL = "/getAreaRiseSetInfo"; + + public void setSunRiseSetOnDb() { + + // time columns + List timeColumn = List.of("sunrise", "suntransit", "sunset", "moonrise", "moontransit", "moonset", "civilm", "civile", "nautm", "naute", "astm", "aste"); + // TODO db에서 누락된 데이터를 조회하여 처리 + + + // continue 20231202 + List locations = getLocation(); +// LocalDate targetDate = LocalDate.now(); + LocalDate targetDate = LocalDate.of(2023, 12, 1); + LocalDate end = targetDate.plusMonths(12); + List sunRiseSet = new ArrayList<>(); + + int cnt = 0; + while (targetDate.isBefore(end) || targetDate.equals(end)) { + String locdate = targetDate.format(DateTimeFormatter.ofPattern("yyyyMMdd")); + for (String location : locations) { + try { + cnt++; + List results = getSunRiseSet(locdate, location); + // trim 제거 및 6자리로 맞추기 + results.forEach(result -> { + Class aClass = result.getClass(); + Field[] declaredFields = aClass.getDeclaredFields(); + for (Field field: declaredFields){ + if(timeColumn.contains(field.getName())){ + field.setAccessible(true); + try { + Object o = field.get(result); + if(o instanceof String){ + String str = (String) o; + if(!str.isEmpty()) { + String trim = str.trim(); + if(trim.length() < 6 && trim.length() >= 4) { + field.set(result, String.format("%-6s", trim).replace(' ', '0')); + } else { + field.set(result, null); + } + } + } + + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + } + }); + sunRiseSet.addAll(results); + log.info(">>> sunRiseSet : {}", results); + } catch (Exception e) { + log.info("sunrise/sunset fail get date :: date:{}/location:{}", locdate, location); + } + } + for(SunRiseSetRs rs : sunRiseSet) { + ComRiseSetBasPK pk = SunRiseSetMapper.MAPPER.toEntityPk(rs); + ComRiseSetBas entity = SunRiseSetMapper.MAPPER.toEntity(rs); + entity.setId(pk); + + comRiseSetBasRepository.save(entity); + } + sunRiseSet.clear(); +// if (++cnt > 500) break; + targetDate = targetDate.plusDays(1); + } + log.info(">>>> call cnt : {}", cnt); + + } + + + public List getSunRiseSet(String locdate, String location) { + + String uriStr = UriComponentsBuilder.fromUriString(AREA_RISE_SEET_INFO_URL) + .queryParam("serviceKey", SUN_KEY) + .queryParam("locdate", locdate) + .queryParam("location", location) + .build().toUriString(); + + WebClient client = WebClient.builder() + .baseUrl(SUN_HOST) + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) + .build(); + + ResponseEntity resp = client.get() + .uri(uriStr) + .retrieve() + .toEntity(SunRiseSetXmlRs.class) + .block(); + + log.debug(">>> resp : {}", resp); + + List items = resp.getBody().getBody().getItems(); + + return items; + } + + private List getLocation() { + String locationStr = "가거도, 가평, 강릉, 강원고성, 강진, 강화, 거제, 거창, 격렬비도, 경기광주, 경산, 경주, 경주시감포읍, 경주시산내면, 계룡, 고령, 고성, 고양, 고창, 고흥, 곡성, 공주, 과천, 광명, 광양, 광주, 괴산, 괴산군연풍면, 구례, 구리, 구미, 군산, 군위, 군포, 금산, 기장, 김제, 김천, 김천시부항면, 김포, 김해, 나주, 남양주, 남원, 남해, 논산, 단양, 달성, 담양, 당진, 대관령, 대구, 대덕전파천문대, 대전, 덕적도, 독도, 동두천, 동해, 마라도, 목포, 무안, 무주, 문경, 밀양, 백령도, 변산, 보령, 보성, 보은, 보현산천문대, 봉화, 부산, 부안, 부여, 부천, 사천, 산청, 삼산도, 삼척, 상주, 서귀포, 서산, 서울, 서천, 성남, 성산일출, 성주, 세종, 소백산천문대, 속초, 수원, 순창, 순천, 승주, 시흥, 신안, 신안군임자면, 아산, 안동, 안산, 안성, 안양, 양구, 양산, 양양, 양주, 양평, 어청도, 여수, 여수공항, 여주, 연세KVN, 연천, 연평도, 영광, 영덕, 영동, 영암, 영양, 영월, 영주, 영천, 예산, 예천, 오산, 옥천, 옹진, 완도, 완주, 용인, 울릉, 울산, 울산KVN, 울주, 울진, 원주, 위도, 음성, 의령, 의성, 의왕, 의정부, 이천, 익산, 인제, 인제군기린면, 인천, 임실, 장성, 장수, 장흥, 전주, 정선, 정읍, 제주, 제주레이더, 제천, 주문진, 증평, 진도, 진안, 진주, 진천, 진해, 창녕, 창원, 천안, 철원, 청도, 청송, 청양, 청주, 청주공항, 추자도, 추풍령, 춘양, 춘천, 충주, 칠곡, 탐라KVN, 태백, 태안, 통영, 파주, 평창, 평창KVN, 평택, 포천, 포항, 하남, 하동, 함안, 함양, 함평, 합천, 해남, 홍성, 홍천, 홍천군내면, 홍천군서석면, 화성, 화순, 화천, 횡성, 흑산도"; +// String locationStr = "김포"; + return Arrays.stream(locationStr.split(",")).map(String::trim).distinct().filter(str -> !str.isEmpty()).collect(Collectors.toList()); + } + + public static void main(String[] args) { + // 현재 시각을 가져옵니다. + Instant now = Instant.now(); + + // 3개월을 더합니다. + Instant threeMonthsLater = now.plus(Period.ofMonths(3)); + + System.out.println("현재 시각: " + now); + System.out.println("3개월 후 시각: " + threeMonthsLater); + } +} diff --git a/pav-server/src/main/java/com/palnet/biz/jpa/entity/ComRiseSetBas.java b/pav-server/src/main/java/com/palnet/biz/jpa/entity/ComRiseSetBas.java new file mode 100644 index 00000000..c511101f --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/jpa/entity/ComRiseSetBas.java @@ -0,0 +1,63 @@ +package com.palnet.biz.jpa.entity; + +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; +import org.springframework.data.annotation.CreatedDate; + +import javax.persistence.*; +import java.time.Instant; + +/** + * packageName : com.palnet.biz.jpa.entity + * fileName : ComRiseSetBas + * author : dhji + * date : 2023-10-15(015) + * description : + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2023-10-15(015) dhji 최초 생성 + */ +@Data +@Entity +@Table(name = "COM_RISE_SET_BAS") +public class ComRiseSetBas { + + @EmbeddedId + private ComRiseSetBasPK id; + @Column(name = "LON") + private Double lon; + @Column(name = "LAT") + private Double lat; + @Column(name = "SUNRISE") + private String sunrise; + @Column(name = "SUNTRANSIT") + private String suntransit; + @Column(name = "SUNSET") + private String sunset; + @Column(name = "MOONRISE") + private String moonrise; + @Column(name = "MOONTRANSIT") + private String moontransit; + @Column(name = "MOONSET") + private String moonset; + @Column(name = "CIVILM") + private String civilm; + @Column(name = "CIVILE") + private String civile; + @Column(name = "NAUTM") + private String nautm; + @Column(name = "NAUTE") + private String naute; + @Column(name = "ASTM") + private String astm; + @Column(name = "ASTE") + private String aste; + @UpdateTimestamp + @Column(name = "UPDATE_DT", columnDefinition = "TIMESTAMP") + private Instant updateDt; + @CreationTimestamp + @Column(name = "CREATE_DT", columnDefinition = "TIMESTAMP", updatable = false) + private Instant createDt; +} diff --git a/pav-server/src/main/java/com/palnet/biz/jpa/entity/ComRiseSetBasPK.java b/pav-server/src/main/java/com/palnet/biz/jpa/entity/ComRiseSetBasPK.java new file mode 100644 index 00000000..2ce55b34 --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/jpa/entity/ComRiseSetBasPK.java @@ -0,0 +1,30 @@ +package com.palnet.biz.jpa.entity; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import java.io.Serializable; + +/** + * packageName : com.palnet.biz.jpa.entity + * fileName : ComRiseSetBasPK + * author : dhji + * date : 2023-10-15(015) + * description : + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2023-10-15(015) dhji 최초 생성 + */ +@Data +@Embeddable +public class ComRiseSetBasPK implements Serializable { + + private static final long serialVersionUID = -2002741457154908827L; + + @Column(name="LOC_DATE") + private String locDate; + @Column(name="LOCATION") + private String location; +} diff --git a/pav-server/src/main/java/com/palnet/biz/jpa/repository/com/ComArcrftBasRepository.java b/pav-server/src/main/java/com/palnet/biz/jpa/repository/com/ComArcrftBasRepository.java index 9745574e..ac9f228b 100644 --- a/pav-server/src/main/java/com/palnet/biz/jpa/repository/com/ComArcrftBasRepository.java +++ b/pav-server/src/main/java/com/palnet/biz/jpa/repository/com/ComArcrftBasRepository.java @@ -1,15 +1,11 @@ package com.palnet.biz.jpa.repository.com; +import com.palnet.biz.jpa.entity.ComArcrftBas; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import com.palnet.biz.jpa.entity.ComArcrftBas; -import com.palnet.biz.jpa.entity.CtrCntrlBas; -import com.palnet.biz.jpa.entity.PtyCstmrGroup; -import com.palnet.biz.jpa.entity.PtyTermsBas; - import java.util.List; @Repository diff --git a/pav-server/src/main/java/com/palnet/biz/jpa/repository/com/ComFileBasRepository.java b/pav-server/src/main/java/com/palnet/biz/jpa/repository/com/ComFileBasRepository.java index 95a92c70..3fb74f1f 100644 --- a/pav-server/src/main/java/com/palnet/biz/jpa/repository/com/ComFileBasRepository.java +++ b/pav-server/src/main/java/com/palnet/biz/jpa/repository/com/ComFileBasRepository.java @@ -2,11 +2,10 @@ package com.palnet.biz.jpa.repository.com; import java.util.List; +import com.palnet.biz.jpa.entity.ComFileBas; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import com.palnet.biz.jpa.entity.ComFileBas; - @Repository public interface ComFileBasRepository extends JpaRepository{ diff --git a/pav-server/src/main/java/com/palnet/biz/jpa/repository/com/ComRiseSetBasRepository.java b/pav-server/src/main/java/com/palnet/biz/jpa/repository/com/ComRiseSetBasRepository.java new file mode 100644 index 00000000..60fd9042 --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/jpa/repository/com/ComRiseSetBasRepository.java @@ -0,0 +1,28 @@ +package com.palnet.biz.jpa.repository.com; + +import com.palnet.biz.jpa.entity.ComRiseSetBas; +import com.palnet.biz.jpa.entity.ComRiseSetBasPK; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +/** + * packageName : com.palnet.biz.jpa.repository.com + * fileName : ComRiseSetBasRepository + * author : dhji + * date : 2023-10-15(015) + * description : + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2023-10-15(015) dhji 최초 생성 + */ +public interface ComRiseSetBasRepository extends JpaRepository { + @Query(value = "SELECT * FROM COM_RISE_SET_BAS WHERE LOC_DATE >= :locStDate", nativeQuery = true) + List findByLocStDate(@Param("locStDate") String locStDate); + + @Query(value = "SELECT R FROM COM_RISE_SET_BAS R WHERE LOC_DATE >= :locStDate AND LOC_DATE <= :locEndDate", nativeQuery = true) + List findByLocStDateAndLocEndDate(@Param("locStDate") String locStDate, @Param("locEndDate") String locEndDate); +} diff --git a/pav-server/src/main/java/com/palnet/biz/jpa/repository/com/ComRiseSetQueryRepository.java b/pav-server/src/main/java/com/palnet/biz/jpa/repository/com/ComRiseSetQueryRepository.java new file mode 100644 index 00000000..8a507806 --- /dev/null +++ b/pav-server/src/main/java/com/palnet/biz/jpa/repository/com/ComRiseSetQueryRepository.java @@ -0,0 +1,93 @@ +package com.palnet.biz.jpa.repository.com; + +import com.palnet.biz.api.comn.sunriseset.model.ComnSunrisesetRq; +import com.palnet.biz.api.comn.sunriseset.model.ComnSunrisesetRs; +import com.palnet.biz.jpa.entity.ComRiseSetBas; +import com.palnet.biz.jpa.entity.QComRiseSetBas; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * packageName : com.palnet.biz.jpa.repository.com + * fileName : ComRiseSetQueryRepository + * author : dhji + * date : 2023-10-15(015) + * description : + * =========================================================== + * DATE AUTHOR NOTE + * ----------------------------------------------------------- + * 2023-10-15(015) dhji 최초 생성 + */ +@Slf4j +@Repository +@RequiredArgsConstructor +public class ComRiseSetQueryRepository { + private final JPAQueryFactory query; + + public List findAllBySearch(ComnSunrisesetRq rq) { + QComRiseSetBas bas = QComRiseSetBas.comRiseSetBas; + + BooleanBuilder builder = new BooleanBuilder(); + if (rq != null) { + if (rq.getLocStDate() != null) { + builder.and(bas.id.locDate.loe(rq.getLocStDate())); + } + if (rq.getLocEndDate() != null) { + builder.and(bas.id.locDate.goe(rq.getLocEndDate())); + } + if (rq.getLocation() != null) { + builder.and(bas.id.location.eq(rq.getLocation())); + } + } + + List results = query.select(bas) + .from(bas) + .where(builder) + .orderBy(bas.id.locDate.asc()) + .fetch(); + + return results; + + } + + public List findAllBySearchTransform(ComnSunrisesetRq rq) { + QComRiseSetBas bas = QComRiseSetBas.comRiseSetBas; + + BooleanBuilder builder = new BooleanBuilder(); + if (rq != null) { + if (rq.getLocStDate() != null) { + builder.and(bas.id.locDate.loe(rq.getLocStDate())); + } + if (rq.getLocEndDate() != null) { + builder.and(bas.id.locDate.goe(rq.getLocEndDate())); + } + if (rq.getLocation() != null) { + builder.and(bas.id.location.eq(rq.getLocation())); + } + } + + List results = query + .select(Projections.bean( + ComnSunrisesetRs.class, + bas.id.locDate, + bas.id.location, + bas.sunrise, + bas.sunset, + bas.civilm, + bas.civile + )) + .from(bas) + .where(builder) + .orderBy(bas.id.locDate.asc()) + .fetch(); + + return results; + + } +} diff --git a/pav-server/src/main/resources/application.properties b/pav-server/src/main/resources/application.properties index e16c2b40..6a13ea41 100644 --- a/pav-server/src/main/resources/application.properties +++ b/pav-server/src/main/resources/application.properties @@ -10,6 +10,11 @@ naver.api.id=5md90yszqj naver.api.key=uCJci6Ixm4DN7hUGtSrjKh6NU6z0SOKq9idpkLat naver.api.url=https://naveropenapi.apigw.ntruss.com/map-reversegeocode/v2/gc +### 일출/일몰 ### +external.sunriseset.url=http://apis.data.go.kr/B090041/openapi/service/RiseSetInfoService +#external.sunriseset.key=b%2FStm6AMT3EwXc5kOvwnjK%2FPB57Ay1WzkIGVx5WaMt0MyRt3TEPgcJ%2Fur45GrnVOZKs2sfIWKUX3BTl8eYwVFw%3D%3D +external.sunriseset.key=b/Stm6AMT3EwXc5kOvwnjK/PB57Ay1WzkIGVx5WaMt0MyRt3TEPgcJ/ur45GrnVOZKs2sfIWKUX3BTl8eYwVFw== + ### Token key ###### spring.jwt.secret=jwtsecretkey spring.jwt.prefix=palnet diff --git a/pav-server/src/test/java/com/palnet/biz/api/external/service/SunRiseSetServiceTest.java b/pav-server/src/test/java/com/palnet/biz/api/external/service/SunRiseSetServiceTest.java new file mode 100644 index 00000000..73cc0929 --- /dev/null +++ b/pav-server/src/test/java/com/palnet/biz/api/external/service/SunRiseSetServiceTest.java @@ -0,0 +1,24 @@ +package com.palnet.biz.api.external.service; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@ActiveProfiles("local") +@SpringBootTest +class SunRiseSetServiceTest { + + @Autowired + private SunRiseSetService service; + + @Test + void getSunRiseSet() { + service.getSunRiseSet("20231231", "김포"); + } + + @Test + void setSunRiseSetOnDb() { + service.setSunRiseSetOnDb(); + } +} \ No newline at end of file