diff --git a/src/main/java/com/poststats/golf/api/CourseApi.java b/src/main/java/com/poststats/golf/api/CourseApi.java new file mode 100644 index 0000000..e0b0552 --- /dev/null +++ b/src/main/java/com/poststats/golf/api/CourseApi.java @@ -0,0 +1,74 @@ +package com.poststats.golf.api; + +import com.brianlong.util.FlexMap; +import com.poststats.golf.api.model.Course; +import com.poststats.golf.service.CourseService; +import com.poststats.transformer.impl.DaoConverter; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Response.Status; +import jakarta.ws.rs.core.SecurityContext; +import org.slf4j.Logger; + +/** + * @author brian.long@poststats.com + */ +@RequestScoped +@Path("/golf/course/{courseId}") +@Tag(name = "Course API") +@ApiResponses({ + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "401", description = "You are not authorized"), + @ApiResponse(responseCode = "403", description = "You are not identified or authenticated") +}) +public class CourseApi { + + @Parameter(name = "courseId", description = "A unique identifier for a golf course") + @PathParam("courseId") + private int courseId; + + @Inject + private Logger logger; + + @Inject + private CourseService courseService; + + @Inject + private DaoConverter converter; + + @PostConstruct + public void init() { + this.logger.debug("CourseApi init: {}", this.courseId); + } + + @GET + @Produces(Constants.V1_JSON) + @Operation( + summary = "Retrieves limited meta-data about a course.", + description = "Retreives name, location, and other direct meta-data about the specified course." + ) + @ApiResponses({ + @ApiResponse(responseCode = "404", description = "A golf course with the specified ID could not be found") + }) + public Course get(@Context SecurityContext scontext) { + FlexMap row = this.courseService.get(this.courseId); + if (row == null) + throw new WebApplicationException("Course not found", Status.NOT_FOUND); + this.logger.trace("found: {}", this.courseId); + + return this.converter.convertValue(row, Course.class); + } + +} diff --git a/src/main/java/com/poststats/golf/api/CoursesApi.java b/src/main/java/com/poststats/golf/api/CoursesApi.java new file mode 100644 index 0000000..3786d78 --- /dev/null +++ b/src/main/java/com/poststats/golf/api/CoursesApi.java @@ -0,0 +1,176 @@ +package com.poststats.golf.api; + +import com.brianlong.util.FlexMap; +import com.brianlong.util.SubList; +import com.poststats.api.model.PagedCollection; +import com.poststats.api.model.Pagination; +import com.poststats.golf.api.model.Course; +import com.poststats.golf.service.CourseService; +import com.poststats.transformer.impl.DaoConverter; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.BeanParam; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Response.Status; +import java.util.List; +import org.apache.http.HttpStatus; +import org.slf4j.Logger; + +/** + * @author brian.long@poststats.com + */ +@RequestScoped +@Path("/golf/courses") +@Tag(name = "Course API") +public class CoursesApi { + + @Inject + private Logger logger; + + @Inject + private CourseService courseService; + + @Inject + private DaoConverter converter; + + @PostConstruct + public void init() { + this.logger.debug("CoursesApi init"); + } + + @GET + @Path("/byName") + @Produces(Constants.V1_JSON) + @Operation( + summary = "Searches for golf courses by their name.", + description = "Searches for golf courses by their name, excluding prefix (e.g. The) and suffix (e.g. Golf Club), returning limited meta-data about each matched golf course." + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "No matching golf courses were found"), + @ApiResponse(responseCode = "411", description = "The page number must be at least 1"), + @ApiResponse(responseCode = "413", description = "The page size is too large") + }) + @Parameters({ + @Parameter( + name = "name", + required = true, + description = "A fragment or whole golf course name; the prefix and suffix are not included" + ), @Parameter(name = "page", description = "A page number, starting at 1", example = "1"), + @Parameter(name = "perPage", description = "A page size", example = "10") + }) + @Tag(name = "Search API") + public PagedCollection> searchByName(@QueryParam("name") String name, @BeanParam Pagination paging) { + SubList rows = this.courseService.findByName(name, paging.getPage(), paging.getPerPage()); + if (rows.isEmpty()) + throw new WebApplicationException("No matching courses found", Status.NOT_FOUND); + this.logger.trace("found: {} [{}-{}] of {}", rows.size(), rows.getStartIndex() + 1, rows.getEndIndex(), + rows.getTotal()); + + return this.converter.convertValue(rows, Course.class).withPage(paging.getPage()) + .withPerPage(paging.getPerPage()); + } + + @GET + @Path("/byLocation/{country}/{state}") + @Produces(Constants.V1_JSON) + @Operation( + summary = "Searches for golf courses by their location.", + description = "Searches for golf courses by their major jurisdiction (e.g. US States), returning limited meta-data about each matched golf course." + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse( + responseCode = "404", + description = "The country, state, or matching golf courses were not found" + ), @ApiResponse(responseCode = "411", description = "The page number must be at least 1"), + @ApiResponse(responseCode = "413", description = "The page size is too large") + }) + @Parameters({ + @Parameter(name = "country", required = true, description = "A country code", example = "US"), + @Parameter(name = "state", description = "A State or high-level jurisdiction", example = "FL"), + @Parameter(name = "page", description = "A page number, starting at 1", example = "1"), + @Parameter(name = "perPage", description = "A page size", example = "10") + }) + @Tag(name = "Search API") + public PagedCollection> searchByJurisdiction(@PathParam("country") String country, + @PathParam("state") String state, @BeanParam Pagination paging) { + SubList rows = this.courseService.findByJurisdiction(country, state, paging.getPage(), + paging.getPerPage()); + if (rows.isEmpty()) + throw new WebApplicationException("No matching golf courses found", Status.NOT_FOUND); + + return this.converter.convertValue(rows, Course.class).withPage(paging.getPage()) + .withPerPage(paging.getPerPage()); + } + + @GET + @Path("/byGeo") + @Produces(Constants.V1_JSON) + @Operation( + summary = "Searches for golf courses by their location.", + description = "Searches for golf courses by their major jurisdiction (e.g. US States), returning limited meta-data about each matched golf course." + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse( + responseCode = "400", + description = "A latitude/longitude/radius was outside its natural limits" + ), @ApiResponse(responseCode = "404", description = "No golf courses were found"), + @ApiResponse(responseCode = "411", description = "The page number must be at least 1"), + @ApiResponse(responseCode = "413", description = "The page size is too large") + }) + @Parameters({ + @Parameter( + name = "latitude", + required = true, + description = "A latitude in decimal degrees", + example = "41.2390" + ), + @Parameter( + name = "longitude", + required = true, + description = "A longitude in decimal degrees", + example = "-81.34891" + ), @Parameter(name = "radius", description = "A search radius in miles", example = "10"), + @Parameter(name = "page", description = "A page number, starting at 1", example = "1"), + @Parameter(name = "perPage", description = "A page size", example = "20") + }) + @Tag(name = "Search API") + public PagedCollection> searchByJurisdiction(@QueryParam("latitude") double latitude, + @QueryParam("longitude") double longitude, @QueryParam("radius") Integer radiusInMiles, + @BeanParam Pagination paging) { + if (latitude < -90.0 || latitude > 90.0) + throw new WebApplicationException("A latitude of -90 to 90 is expected", HttpStatus.SC_BAD_REQUEST); + if (longitude < -180.0 || longitude > 180.0) + throw new WebApplicationException("A longitude of -180 to 180 is expected", HttpStatus.SC_BAD_REQUEST); + + if (radiusInMiles == null) + radiusInMiles = 10; + if (radiusInMiles <= 0) + throw new WebApplicationException("A positive radius is expected", HttpStatus.SC_BAD_REQUEST); + if (radiusInMiles > 100) + throw new WebApplicationException("A radius under 100 miles is expected", HttpStatus.SC_BAD_REQUEST); + + SubList rows = this.courseService.findByLocation(latitude, longitude, radiusInMiles, + paging.getPage(), paging.getPerPage()); + if (rows.isEmpty()) + throw new WebApplicationException("No matching facilities found", Status.NOT_FOUND); + + return this.converter.convertValue(rows, Course.class).withPage(paging.getPage()) + .withPerPage(paging.getPerPage()); + } + +} diff --git a/src/main/java/com/poststats/golf/api/EventRoundApi.java b/src/main/java/com/poststats/golf/api/EventRoundApi.java new file mode 100644 index 0000000..629668a --- /dev/null +++ b/src/main/java/com/poststats/golf/api/EventRoundApi.java @@ -0,0 +1,112 @@ +package com.poststats.golf.api; + +import com.brianlong.util.FlexMap; +import com.poststats.api.Constants; +import com.poststats.golf.api.model.EventRound; +import com.poststats.golf.service.EventRoundService; +import com.poststats.transformer.impl.DaoConverter; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.Response.Status; +import java.util.List; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author brian.long@poststats.com + */ +@RequestScoped +@Path("/golf/event/{eventId}") +@Tag(name = "Event Round API") +public class EventRoundApi { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @PathParam("eventId") + private long eventId; + + @Inject + private EventRoundService roundService; + + @Inject + private DaoConverter converter; + + @PostConstruct + public void init() { + this.logger.debug("EventRoundApi init: {}", this.eventId); + } + + @GET + @Path("/r{number}") + @Produces(Constants.V1_JSON) + @Operation( + summary = "Retrieves limited meta-data about an event.", + description = "Retreives name, location, dates, and other direct meta-data about the specified event." + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "An event with the specified ID could not be found") + }) + public EventRound getOne(@PathParam("number") short roundNumber) { + FlexMap row = this.roundService.get(this.eventId, roundNumber); + if (row == null) + throw new WebApplicationException("Event round not found", Status.NOT_FOUND); + + return this.converter.convertValue(row, EventRound.class); + } + + @GET + @Path("/round/{eroundId}") + @Produces(Constants.V1_JSON) + @Operation( + summary = "Retrieves limited meta-data about an event.", + description = "Retreives name, location, dates, and other direct meta-data about the specified event." + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "An event with the specified ID could not be found") + }) + public EventRound getOne(@PathParam("eroundId") long eroundId) { + FlexMap row = this.roundService.get(eroundId); + if (row == null) + throw new WebApplicationException("Event round not found", Status.NOT_FOUND); + if (this.eventId != row.getLong("eventID")) { + this.logger.warn("The event round {} was requested without the appropriate event ID {}", eroundId, + this.eventId); + throw new WebApplicationException("Event round not found", Status.NOT_FOUND); + } + + return this.converter.convertValue(row, EventRound.class); + } + + @GET + @Path("/rounds") + @Produces(Constants.V1_JSON) + @Operation( + summary = "Retrieves limited meta-data about an event.", + description = "Retreives name, location, dates, and other direct meta-data about the specified event." + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "404", description = "An event with the specified ID could not be found") + }) + public List getAll() { + Map rows = this.roundService.getByEventId(this.eventId); + if (rows.isEmpty()) + throw new WebApplicationException("No event rounds found", Status.NOT_FOUND); + + return this.converter.convertValue(rows.values(), EventRound.class); + } + +} diff --git a/src/main/java/com/poststats/golf/api/model/BaseCourse.java b/src/main/java/com/poststats/golf/api/model/BaseCourse.java new file mode 100644 index 0000000..7959180 --- /dev/null +++ b/src/main/java/com/poststats/golf/api/model/BaseCourse.java @@ -0,0 +1,109 @@ +package com.poststats.golf.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.poststats.api.model.BaseModel; +import com.poststats.transformer.MapEntry; +import java.time.Year; + +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class BaseCourse> extends BaseModel { + + public enum Access { + @JsonProperty("private") + @MapEntry("private") + Private, @JsonProperty("military") + @MapEntry("military") + Military, @JsonProperty("semi-private") + @MapEntry("semi-private") + SemiPrivate, @JsonProperty("public") + @MapEntry("public") + Public, + } + + @JsonProperty + @MapEntry("course") + private String name; + + @JsonProperty + @MapEntry("coursePrefix") + private String namePrefix; + + @JsonProperty + @MapEntry + private Access access; + + @JsonProperty + @MapEntry + private Year opened; + + @JsonProperty + @MapEntry + private String architect; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getNamePrefix() { + return namePrefix; + } + + public void setNamePrefix(String namePrefix) { + this.namePrefix = namePrefix; + } + + public Access getAccess() { + return access; + } + + public void setAccess(Access access) { + this.access = access; + } + + public Year getOpened() { + return opened; + } + + public void setOpened(Year opened) { + this.opened = opened; + } + + public String getArchitect() { + return architect; + } + + public void setArchitect(String architect) { + this.architect = architect; + } + + public ConcreteT withName(String name) { + this.name = name; + return this.withThis(); + } + + public ConcreteT withNamePrefix(String prefix) { + this.namePrefix = prefix; + return this.withThis(); + } + + public ConcreteT withAccess(Access access) { + this.access = access; + return this.withThis(); + } + + public ConcreteT withOpened(Year opened) { + this.opened = opened; + return this.withThis(); + } + + public ConcreteT withArchitect(String architect) { + this.architect = architect; + return this.withThis(); + } + +} diff --git a/src/main/java/com/poststats/golf/api/model/BaseEvent.java b/src/main/java/com/poststats/golf/api/model/BaseEvent.java new file mode 100644 index 0000000..210d0e6 --- /dev/null +++ b/src/main/java/com/poststats/golf/api/model/BaseEvent.java @@ -0,0 +1,92 @@ +package com.poststats.golf.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.poststats.api.model.BaseModel; +import com.poststats.transformer.MapEntry; + +/** + * @author brian.long@poststats.com + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class BaseEvent> extends BaseModel { + + public enum EventType { + @JsonProperty("outing") + @MapEntry("outing") + Outing, @JsonProperty("tourney") + @MapEntry("tourney") + Tourney, @JsonProperty("trip") + @MapEntry("trip") + Trip, @JsonProperty("league") + @MapEntry("league") + League + } + + @JsonProperty(required = true) + @MapEntry("event") + private String name; + + @JsonProperty(required = true) + @MapEntry + private EventType type; + + @JsonProperty + private boolean showPublic; + + @JsonProperty + private String fuzzyLocation; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public EventType getType() { + return type; + } + + public void setType(EventType type) { + this.type = type; + } + + public boolean isShowPublic() { + return showPublic; + } + + public void setShowPublic(boolean showPublic) { + this.showPublic = showPublic; + } + + public String getFuzzyLocation() { + return fuzzyLocation; + } + + public void setFuzzyLocation(String fuzzyLocation) { + this.fuzzyLocation = fuzzyLocation; + } + + public ConcreteT withName(String name) { + this.name = name; + return this.withThis(); + } + + public ConcreteT withType(EventType type) { + this.type = type; + return this.withThis(); + } + + public ConcreteT withShowPublic(boolean showPublic) { + this.showPublic = showPublic; + return this.withThis(); + } + + public ConcreteT withFuzzyLocation(String fuzzyLocation) { + this.fuzzyLocation = fuzzyLocation; + return this.withThis(); + } + +} diff --git a/src/main/java/com/poststats/golf/api/model/BaseEventRound.java b/src/main/java/com/poststats/golf/api/model/BaseEventRound.java new file mode 100644 index 0000000..cb34ebb --- /dev/null +++ b/src/main/java/com/poststats/golf/api/model/BaseEventRound.java @@ -0,0 +1,95 @@ +package com.poststats.golf.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.poststats.api.model.BaseModel; +import com.poststats.transformer.MapEntry; +import java.time.LocalDate; + +/** + * @author brian.long@poststats.com + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class BaseEventRound> extends BaseModel { + + public enum TeeFormat { + @JsonProperty("straight") + @MapEntry("straight") + Straight, @JsonProperty("dual") + @MapEntry("dual") + Dual, @JsonProperty("shotgun-9") + @MapEntry("shotgun-9") + Shotgun9Hole, @JsonProperty("shotgun-18") + @MapEntry("shotgun-18") + Shotgun18Hole + } + + @JsonProperty + @MapEntry("round") + private Short number; + + @JsonProperty + @MapEntry("title") + private String name; + + @JsonProperty + @MapEntry + private LocalDate date; + + @JsonProperty + @MapEntry("teeFormatSID") + private TeeFormat teeFormat; + + public Short getNumber() { + return number; + } + + public void setNumber(Short number) { + this.number = number; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public LocalDate getDate() { + return date; + } + + public void setDate(LocalDate date) { + this.date = date; + } + + public TeeFormat getTeeFormat() { + return teeFormat; + } + + public void setTeeFormat(TeeFormat teeFormat) { + this.teeFormat = teeFormat; + } + + public ConcreteT withNumber(Short number) { + this.number = number; + return this.withThis(); + } + + public ConcreteT withName(String name) { + this.name = name; + return this.withThis(); + } + + public ConcreteT withDate(LocalDate date) { + this.date = date; + return this.withThis(); + } + + public ConcreteT withTeeFormat(TeeFormat teeFormat) { + this.teeFormat = teeFormat; + return this.withThis(); + } + +} diff --git a/src/main/java/com/poststats/golf/api/model/BaseGolfer.java b/src/main/java/com/poststats/golf/api/model/BaseGolfer.java new file mode 100644 index 0000000..bf4c93b --- /dev/null +++ b/src/main/java/com/poststats/golf/api/model/BaseGolfer.java @@ -0,0 +1,46 @@ +package com.poststats.golf.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.poststats.api.model.BaseModel; +import com.poststats.transformer.MapEntry; +import java.time.Year; + +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class BaseGolfer> extends BaseModel { + + @JsonProperty + @MapEntry + private Year started; + + @JsonProperty + @MapEntry("aStrokeHandicap") + private Float strokeHandicap; + + public Year getStarted() { + return started; + } + + public void setStarted(Year started) { + this.started = started; + } + + public Float getStrokeHandicap() { + return strokeHandicap; + } + + public void setStrokeHandicap(Float strokeHandicap) { + this.strokeHandicap = strokeHandicap; + } + + public ConcreteT withStarted(Year started) { + this.started = started; + return this.withThis(); + } + + public ConcreteT withStrokeHandicap(Float strokeHandicap) { + this.strokeHandicap = strokeHandicap; + return this.withThis(); + } + +} diff --git a/src/main/java/com/poststats/golf/api/model/BaseSeries.java b/src/main/java/com/poststats/golf/api/model/BaseSeries.java new file mode 100644 index 0000000..9d2645d --- /dev/null +++ b/src/main/java/com/poststats/golf/api/model/BaseSeries.java @@ -0,0 +1,31 @@ +package com.poststats.golf.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.poststats.api.model.BaseModel; +import com.poststats.transformer.MapEntry; + +/** + * @author brian.long@poststats.com + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class BaseSeries> extends BaseModel { + + @JsonProperty(required = true) + @MapEntry("series") + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public ConcreteT withName(String name) { + this.name = name; + return this.withThis(); + } + +} diff --git a/src/main/java/com/poststats/golf/api/model/Course.java b/src/main/java/com/poststats/golf/api/model/Course.java new file mode 100644 index 0000000..4121ded --- /dev/null +++ b/src/main/java/com/poststats/golf/api/model/Course.java @@ -0,0 +1,63 @@ +package com.poststats.golf.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.poststats.api.model.Facility; +import com.poststats.transformer.MapEntry; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Course extends BaseCourse implements ReferenceableCourse { + + @JsonProperty(required = true, access = com.fasterxml.jackson.annotation.JsonProperty.Access.READ_ONLY) + @MapEntry("courseID") + private int id; + + @JsonProperty + @MapEntry + private Facility facility; + + @JsonProperty + @MapEntry("facilityID") + private Integer facilityId; + + @Override + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Facility getFacility() { + return facility; + } + + public void setFacility(Facility facility) { + this.facility = facility; + } + + public Integer getFacilityId() { + return facilityId; + } + + public void setFacilityId(Integer facilityId) { + this.facilityId = facilityId; + } + + public Course withId(int id) { + this.id = id; + return this; + } + + public Course withFacility(Facility facility) { + this.facility = facility; + return this; + } + + public Course withFacilityId(Integer facilityId) { + this.facilityId = facilityId; + return this; + } + +} diff --git a/src/main/java/com/poststats/golf/api/model/Event.java b/src/main/java/com/poststats/golf/api/model/Event.java index bc0955d..88fc375 100644 --- a/src/main/java/com/poststats/golf/api/model/Event.java +++ b/src/main/java/com/poststats/golf/api/model/Event.java @@ -2,6 +2,7 @@ package com.poststats.golf.api.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; import com.poststats.golf.api.Constants; import com.poststats.service.impl.DefaultFormattingService; import com.poststats.transformer.GeocodeSource; @@ -9,16 +10,15 @@ import com.poststats.transformer.GeocodeTarget; import com.poststats.transformer.GeocodeTarget.GeocodeField; import com.poststats.transformer.MapCondition; import com.poststats.transformer.MapEntry; -import jakarta.annotation.Generated; import java.time.LocalDate; /** * @author brian.long@poststats.com */ @JsonIgnoreProperties(ignoreUnknown = true) -public class Event extends TransientEvent { +public class Event extends BaseEvent implements ReferenceableEvent { - @JsonProperty(required = true) + @JsonProperty(required = true, access = Access.READ_ONLY) @MapEntry("eventID") private long id; @@ -46,6 +46,10 @@ public class Event extends TransientEvent { @MapEntry private Series series; + @JsonProperty(access = Access.READ_ONLY) + @MapEntry("seriesID") + private Integer seriesId; + @JsonProperty @MapEntry private Boolean financesEnabled; @@ -150,32 +154,27 @@ public class Event extends TransientEvent { }) private LocalDate specialHolesLiveline; - @Generated("Eclipse") + @Override public long getId() { return id; } - @Generated("Eclipse") public void setId(long id) { this.id = id; } - @Generated("Eclipse") public LocalDate getLiveline() { return liveline; } - @Generated("Eclipse") public void setLiveline(LocalDate liveline) { this.liveline = liveline; } - @Generated("Eclipse") public LocalDate getDeadline() { return deadline; } - @Generated("Eclipse") public void setDeadline(LocalDate deadline) { this.deadline = deadline; } @@ -202,6 +201,14 @@ public class Event extends TransientEvent { this.series = series; } + public Integer getSeriesId() { + return seriesId; + } + + public void setSeriesId(Integer seriesId) { + this.seriesId = seriesId; + } + public Boolean getAwardsEnabled() { return awardsEnabled; } @@ -362,19 +369,16 @@ public class Event extends TransientEvent { this.teamsLiveline = teamsLiveline; } - @Generated("Spark") public Event withId(long id) { this.id = id; return this; } - @Generated("Spark") public Event withLiveline(LocalDate liveline) { this.liveline = liveline; return this; } - @Generated("Spark") public Event withDeadline(LocalDate deadline) { this.deadline = deadline; return this; @@ -385,6 +389,11 @@ public class Event extends TransientEvent { return this; } + public Event withSeriesId(Integer seriesId) { + this.seriesId = seriesId; + return this; + } + public Event withDocumentsEnabled(Boolean documentsEnabled) { this.documentsEnabled = documentsEnabled; return this; diff --git a/src/main/java/com/poststats/golf/api/model/EventRound.java b/src/main/java/com/poststats/golf/api/model/EventRound.java new file mode 100644 index 0000000..0f6add7 --- /dev/null +++ b/src/main/java/com/poststats/golf/api/model/EventRound.java @@ -0,0 +1,66 @@ +package com.poststats.golf.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; +import com.poststats.transformer.MapEntry; + +/** + * @author brian.long@poststats.com + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class EventRound extends BaseEventRound implements ReferenceableEventRound { + + @JsonProperty(required = true, access = Access.READ_ONLY) + @MapEntry("eroundID") + private long id; + + @JsonProperty + @MapEntry + private Course course; + + @JsonProperty + @MapEntry("courseID") + private Integer courseId; + + @Override + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Course getCourse() { + return course; + } + + public void setCourse(Course course) { + this.course = course; + } + + public Integer getCourseId() { + return courseId; + } + + public void setCourseId(Integer courseId) { + this.courseId = courseId; + } + + public EventRound withId(long id) { + this.id = id; + return this; + } + + public EventRound withCourse(Course course) { + this.course = course; + return this; + } + + public EventRound withCourseId(Integer courseId) { + this.courseId = courseId; + return this; + } + +} diff --git a/src/main/java/com/poststats/golf/api/model/Golfer.java b/src/main/java/com/poststats/golf/api/model/Golfer.java index fd87e09..e0d8510 100644 --- a/src/main/java/com/poststats/golf/api/model/Golfer.java +++ b/src/main/java/com/poststats/golf/api/model/Golfer.java @@ -4,24 +4,16 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty.Access; import com.poststats.api.model.Person; +import com.poststats.api.model.ReferenceablePerson; import com.poststats.transformer.MapEntry; -import java.time.Year; @JsonIgnoreProperties(ignoreUnknown = true) -public class Golfer { +public class Golfer extends BaseGolfer implements ReferenceablePerson { @JsonProperty(required = true, access = Access.READ_ONLY) @MapEntry("personID") private long personId; - @JsonProperty - @MapEntry - private Year started; - - @JsonProperty - @MapEntry("aStrokeHandicap") - private Float strokeHandicap; - @JsonProperty @MapEntry("unofficialStrokeHandicap") private Float poststatsStrokeHandicap; @@ -34,30 +26,15 @@ public class Golfer { @MapEntry private Person person; - public long getPersonId() { + @Override + public long getId() { return personId; } - public void setPersonId(long personId) { + public void setId(long personId) { this.personId = personId; } - public Year getStarted() { - return started; - } - - public void setStarted(Year started) { - this.started = started; - } - - public Float getStrokeHandicap() { - return strokeHandicap; - } - - public void setStrokeHandicap(Float strokeHandicap) { - this.strokeHandicap = strokeHandicap; - } - public Float getPoststatsStrokeHandicap() { return poststatsStrokeHandicap; } @@ -87,16 +64,6 @@ public class Golfer { return this; } - public Golfer withStarted(Year started) { - this.started = started; - return this; - } - - public Golfer withStrokeHandicap(Float strokeHandicap) { - this.strokeHandicap = strokeHandicap; - return this; - } - public Golfer withPoststatsStrokeHandicap(Float poststatsStrokeHandicap) { this.poststatsStrokeHandicap = poststatsStrokeHandicap; return this; diff --git a/src/main/java/com/poststats/golf/api/model/ReferenceableCourse.java b/src/main/java/com/poststats/golf/api/model/ReferenceableCourse.java new file mode 100644 index 0000000..c671260 --- /dev/null +++ b/src/main/java/com/poststats/golf/api/model/ReferenceableCourse.java @@ -0,0 +1,7 @@ +package com.poststats.golf.api.model; + +public interface ReferenceableCourse { + + int getId(); + +} diff --git a/src/main/java/com/poststats/golf/api/model/ReferenceableEvent.java b/src/main/java/com/poststats/golf/api/model/ReferenceableEvent.java new file mode 100644 index 0000000..0902c3b --- /dev/null +++ b/src/main/java/com/poststats/golf/api/model/ReferenceableEvent.java @@ -0,0 +1,7 @@ +package com.poststats.golf.api.model; + +public interface ReferenceableEvent { + + long getId(); + +} diff --git a/src/main/java/com/poststats/golf/api/model/ReferenceableEventRound.java b/src/main/java/com/poststats/golf/api/model/ReferenceableEventRound.java new file mode 100644 index 0000000..61bb0e0 --- /dev/null +++ b/src/main/java/com/poststats/golf/api/model/ReferenceableEventRound.java @@ -0,0 +1,7 @@ +package com.poststats.golf.api.model; + +public interface ReferenceableEventRound { + + long getId(); + +} diff --git a/src/main/java/com/poststats/golf/api/model/ReferenceableSeries.java b/src/main/java/com/poststats/golf/api/model/ReferenceableSeries.java new file mode 100644 index 0000000..ccd0643 --- /dev/null +++ b/src/main/java/com/poststats/golf/api/model/ReferenceableSeries.java @@ -0,0 +1,7 @@ +package com.poststats.golf.api.model; + +public interface ReferenceableSeries { + + int getId(); + +} diff --git a/src/main/java/com/poststats/golf/api/model/ReferencedCourse.java b/src/main/java/com/poststats/golf/api/model/ReferencedCourse.java new file mode 100644 index 0000000..5d4929c --- /dev/null +++ b/src/main/java/com/poststats/golf/api/model/ReferencedCourse.java @@ -0,0 +1,29 @@ +package com.poststats.golf.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; +import com.poststats.transformer.MapEntry; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ReferencedCourse implements ReferenceableCourse { + + @JsonProperty(required = true, access = Access.READ_ONLY) + @MapEntry("courseID") + private int id; + + @Override + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public ReferencedCourse withId(int id) { + this.id = id; + return this; + } + +} diff --git a/src/main/java/com/poststats/golf/api/model/ReferencedEvent.java b/src/main/java/com/poststats/golf/api/model/ReferencedEvent.java new file mode 100644 index 0000000..f0411c1 --- /dev/null +++ b/src/main/java/com/poststats/golf/api/model/ReferencedEvent.java @@ -0,0 +1,32 @@ +package com.poststats.golf.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; +import com.poststats.transformer.MapEntry; + +/** + * @author brian.long@poststats.com + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ReferencedEvent implements ReferenceableEvent { + + @JsonProperty(required = true, access = Access.READ_ONLY) + @MapEntry("eventID") + private long id; + + @Override + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public ReferencedEvent withId(long id) { + this.id = id; + return this; + } + +} diff --git a/src/main/java/com/poststats/golf/api/model/ReferencedEventRound.java b/src/main/java/com/poststats/golf/api/model/ReferencedEventRound.java new file mode 100644 index 0000000..00c5788 --- /dev/null +++ b/src/main/java/com/poststats/golf/api/model/ReferencedEventRound.java @@ -0,0 +1,32 @@ +package com.poststats.golf.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; +import com.poststats.transformer.MapEntry; + +/** + * @author brian.long@poststats.com + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ReferencedEventRound implements ReferenceableEvent { + + @JsonProperty(required = true, access = Access.READ_ONLY) + @MapEntry("eroundID") + private long id; + + @Override + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public ReferencedEventRound withId(long id) { + this.id = id; + return this; + } + +} diff --git a/src/main/java/com/poststats/golf/api/model/ReferencedSeries.java b/src/main/java/com/poststats/golf/api/model/ReferencedSeries.java new file mode 100644 index 0000000..63dfd42 --- /dev/null +++ b/src/main/java/com/poststats/golf/api/model/ReferencedSeries.java @@ -0,0 +1,27 @@ +package com.poststats.golf.api.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; +import com.poststats.transformer.MapEntry; + +public class ReferencedSeries implements ReferenceableSeries { + + @JsonProperty(required = true, access = Access.READ_ONLY) + @MapEntry("seriesID") + private int id; + + @Override + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public ReferencedSeries withId(int id) { + this.id = id; + return this; + } + +} diff --git a/src/main/java/com/poststats/golf/api/model/Series.java b/src/main/java/com/poststats/golf/api/model/Series.java index 28cedfc..3140906 100644 --- a/src/main/java/com/poststats/golf/api/model/Series.java +++ b/src/main/java/com/poststats/golf/api/model/Series.java @@ -4,30 +4,27 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty.Access; import com.poststats.transformer.MapEntry; -import jakarta.annotation.Generated; /** * @author brian.long@poststats.com */ @JsonIgnoreProperties(ignoreUnknown = true) -public class Series extends TransientSeries { +public class Series extends BaseSeries implements ReferenceableSeries { @JsonProperty(required = true, access = Access.READ_ONLY) @MapEntry("seriesID") - private long id; + private int id; - @Generated("Eclipse") - public long getId() { + @Override + public int getId() { return id; } - @Generated("Eclipse") - public void setId(long id) { + public void setId(int id) { this.id = id; } - @Generated("Spark") - public Series withId(long id) { + public Series withId(int id) { this.id = id; return this; } diff --git a/src/main/java/com/poststats/golf/api/model/TransientCourse.java b/src/main/java/com/poststats/golf/api/model/TransientCourse.java new file mode 100644 index 0000000..a65ad35 --- /dev/null +++ b/src/main/java/com/poststats/golf/api/model/TransientCourse.java @@ -0,0 +1,28 @@ +package com.poststats.golf.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.poststats.api.model.ReferenceableFacility; +import com.poststats.transformer.MapEntry; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class TransientCourse extends BaseCourse { + + @JsonProperty + @MapEntry + private ReferenceableFacility facility; + + public ReferenceableFacility getFacility() { + return facility; + } + + public void setFacility(ReferenceableFacility facility) { + this.facility = facility; + } + + public TransientCourse withFacility(ReferenceableFacility facility) { + this.facility = facility; + return this; + } + +} diff --git a/src/main/java/com/poststats/golf/api/model/TransientEvent.java b/src/main/java/com/poststats/golf/api/model/TransientEvent.java index 7795a09..b5fdbbf 100644 --- a/src/main/java/com/poststats/golf/api/model/TransientEvent.java +++ b/src/main/java/com/poststats/golf/api/model/TransientEvent.java @@ -4,92 +4,27 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.poststats.transformer.MapEntry; -import jakarta.annotation.Generated; - /** * @author brian.long@poststats.com */ @JsonIgnoreProperties(ignoreUnknown = true) -public class TransientEvent { +public class TransientEvent extends BaseEvent { - public enum EventType { - @JsonProperty("outing") - @MapEntry("outing") - Outing, @JsonProperty("tourney") - @MapEntry("tourney") - Tourney, @JsonProperty("trip") - @MapEntry("trip") - Trip, @JsonProperty("league") - @MapEntry("league") - League - } - - @JsonProperty(required = true) - @MapEntry("event") - private String name; - - @JsonProperty(required = true) + @JsonProperty @MapEntry - private EventType type; + private ReferenceableSeries series; - @JsonProperty - private boolean showPublic; - - @JsonProperty - private String fuzzyLocation; - - @Generated("Eclipse") - public String getName() { - return name; + public ReferenceableSeries getSeries() { + return series; } - @Generated("Eclipse") - public void setName(String name) { - this.name = name; + public void setSeries(ReferenceableSeries series) { + this.series = series; } - public EventType getType() { - return type; - } - - public void setType(EventType type) { - this.type = type; - } - - public boolean isShowPublic() { - return showPublic; - } - - public void setShowPublic(boolean showPublic) { - this.showPublic = showPublic; - } - - public String getFuzzyLocation() { - return fuzzyLocation; - } - - public void setFuzzyLocation(String fuzzyLocation) { - this.fuzzyLocation = fuzzyLocation; - } - - public TransientEvent withName(String name) { - this.name = name; - return this; - } - - public TransientEvent withType(EventType type) { - this.type = type; - return this; - } - - public TransientEvent withShowPublic(boolean showPublic) { - this.showPublic = showPublic; - return this; - } - - public TransientEvent withFuzzyLocation(String fuzzyLocation) { - this.fuzzyLocation = fuzzyLocation; - return this; + public TransientEvent withSeries(ReferenceableSeries series) { + this.series = series; + return this.withThis(); } } diff --git a/src/main/java/com/poststats/golf/api/model/TransientEventRound.java b/src/main/java/com/poststats/golf/api/model/TransientEventRound.java new file mode 100644 index 0000000..2ec4338 --- /dev/null +++ b/src/main/java/com/poststats/golf/api/model/TransientEventRound.java @@ -0,0 +1,30 @@ +package com.poststats.golf.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.poststats.transformer.MapEntry; + +/** + * @author brian.long@poststats.com + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class TransientEventRound extends BaseEventRound { + + @JsonProperty + @MapEntry + private ReferenceableCourse course; + + public ReferenceableCourse getCourse() { + return course; + } + + public void setCourse(ReferenceableCourse course) { + this.course = course; + } + + public TransientEventRound withCourse(ReferenceableCourse course) { + this.course = course; + return this; + } + +} diff --git a/src/main/java/com/poststats/golf/api/model/TransientGolfer.java b/src/main/java/com/poststats/golf/api/model/TransientGolfer.java new file mode 100644 index 0000000..5fc4769 --- /dev/null +++ b/src/main/java/com/poststats/golf/api/model/TransientGolfer.java @@ -0,0 +1,28 @@ +package com.poststats.golf.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.poststats.api.model.ReferenceablePerson; +import com.poststats.transformer.MapEntry; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class TransientGolfer extends BaseGolfer { + + @JsonProperty + @MapEntry + private ReferenceablePerson person; + + public ReferenceablePerson getPerson() { + return person; + } + + public void setPerson(ReferenceablePerson person) { + this.person = person; + } + + public TransientGolfer withPerson(ReferenceablePerson person) { + this.person = person; + return this; + } + +} diff --git a/src/main/java/com/poststats/golf/api/model/TransientSeries.java b/src/main/java/com/poststats/golf/api/model/TransientSeries.java index 1377e99..9f89821 100644 --- a/src/main/java/com/poststats/golf/api/model/TransientSeries.java +++ b/src/main/java/com/poststats/golf/api/model/TransientSeries.java @@ -1,34 +1,11 @@ package com.poststats.golf.api.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.poststats.transformer.MapEntry; -import jakarta.annotation.Generated; /** * @author brian.long@poststats.com */ @JsonIgnoreProperties(ignoreUnknown = true) -public class TransientSeries { - - @JsonProperty(required = true) - @MapEntry("series") - private String name; - - @Generated("Eclipse") - public String getName() { - return name; - } - - @Generated("Eclipse") - public void setName(String name) { - this.name = name; - } - - @Generated("Spark") - public TransientSeries withName(String name) { - this.name = name; - return this; - } +public class TransientSeries extends BaseSeries { } diff --git a/src/main/java/com/poststats/golf/service/CourseService.java b/src/main/java/com/poststats/golf/service/CourseService.java new file mode 100644 index 0000000..8f282cb --- /dev/null +++ b/src/main/java/com/poststats/golf/service/CourseService.java @@ -0,0 +1,53 @@ +package com.poststats.golf.service; + +import com.brianlong.util.FlexMap; +import com.brianlong.util.SubList; +import java.util.Collection; +import java.util.Map; + +public interface CourseService { + + /** + * This method retrieves meta-data about the specified course. + * + * This retrieves the facility meta-data as well. + * + * @param courseId A unique identifier for the course. + * @return A map of meta-data specific to the course. + */ + FlexMap get(int courseId); + + /** + * This method retrieves meta-data about the specified courses. + * + * This retrieves the facility meta-data as well. + * + * @param courseIds Unique identifier for the coursees. + * @return A map of unique identifiers to meta-data specific to the coursees. + */ + Map get(Collection courseIds); + + default SubList findByName(String name) { + return this.findByName(name, 1, 10); + } + + SubList findByName(String name, int page, int perPage); + + default SubList findByJurisdiction(String country, String state) { + return this.findByJurisdiction(country, state, 1, 50); + } + + SubList findByJurisdiction(String country, String state, int page, int perPage); + + default SubList findByLocation(double latitude, double longitude) { + return this.findByLocation(latitude, longitude, 10, 1, 25); + } + + default SubList findByLocation(double latitude, double longitude, int radiusInMiles) { + return this.findByLocation(latitude, longitude, radiusInMiles, 1, 25); + } + + SubList findByLocation(double latitude, double longitude, int radiusInMiles, int page, + int perPage); + +} diff --git a/src/main/java/com/poststats/golf/service/EventRoundService.java b/src/main/java/com/poststats/golf/service/EventRoundService.java new file mode 100644 index 0000000..a9f5874 --- /dev/null +++ b/src/main/java/com/poststats/golf/service/EventRoundService.java @@ -0,0 +1,34 @@ +package com.poststats.golf.service; + +import com.brianlong.util.FlexMap; +import java.util.Map; + +public interface EventRoundService { + + /** + * This method retrieves meta-data about the specified event round. + * + * @param eroundId A unique identifier for the event round. + * @return A map of meta-data specific to the event round. + */ + FlexMap get(long eroundId); + + /** + * This method retrieves meta-data about the specified event round. + * + * @param eventId A unique identifier for the event. + * @param number A sequential number for the event round, starting with 1. + * @return A map of meta-data specific to the event round. + */ + FlexMap get(long eventId, short number); + + /** + * This method retrieves meta-data about the specified event rounds. + * + * @param eventId A unique identifier for the event. + * @return A map of unique identifiers to meta-data specific to each event + * round. + */ + Map getByEventId(long eventId); + +} diff --git a/src/main/java/com/poststats/golf/service/db/CourseServiceDAO.java b/src/main/java/com/poststats/golf/service/db/CourseServiceDAO.java new file mode 100644 index 0000000..681a3cc --- /dev/null +++ b/src/main/java/com/poststats/golf/service/db/CourseServiceDAO.java @@ -0,0 +1,218 @@ +package com.poststats.golf.service.db; + +import com.brianlong.sql.DataSet; +import com.brianlong.sql.FlexPreparedStatement; +import com.brianlong.sql.ResultSubSetFeature; +import com.brianlong.util.FlexMap; +import com.brianlong.util.SubList; +import com.poststats.golf.api.Constants; +import com.poststats.golf.service.CourseService; +import com.poststats.provider.Statement; +import com.poststats.provider.StatementProvider; +import com.poststats.service.FacilityService; +import com.poststats.service.ServiceException; +import com.poststats.service.db.CacheableServiceDAO; +import com.poststats.sql.PostStatsSQL; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import java.sql.SQLException; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +@ApplicationScoped +public class CourseServiceDAO extends CacheableServiceDAO implements CourseService { + + private final int defaultCacheExpirationInSeconds = 600; + + @Inject + private FacilityService facilityService; + + @Override + public FlexMap get(int courseId) { + return this.get(Integer.valueOf(courseId)); + } + + @Override + public FlexMap get(Integer courseId) { + FlexMap row = super.get(Integer.valueOf(courseId)); + if (row == null) + return null; + row.put("facility", this.facilityService.get(row.getInteger("facilityID"))); + + return row; + } + + @Override + public Map get(Collection courseIds) { + Map rows = super.get(courseIds); + + // bulk fetch series from cache/db + Set facilityIds = new HashSet<>(); + for (Entry row : rows.entrySet()) + facilityIds.add(row.getValue().getInteger("facilityId")); + + Map facilities = this.facilityService.get(facilityIds); + for (Entry row : rows.entrySet()) + row.getValue().put("facility", facilities.get(row.getValue().getInteger("facilityId"))); + return rows; + } + + @Override + public SubList findByName(String name, int page, int perPage) { + try { + FlexPreparedStatement fps = this.sqlSelectByName.buildPreparedStatement(); + try { + fps.setVarchar(1, "%" + name + "%"); + fps.setVarchar(2, "%" + name + "%"); + fps.setSmallintU(3, (page - 1) * perPage); + fps.setSmallintU(4, perPage); + return (SubList) fps.executeQuery().getAllRows(); + } finally { + fps.close(); + } + } catch (SQLException se) { + throw new ServiceException(se); + } + } + + @Inject + @Named(Constants.STATEMENT_PROVIDER_GOLF) + @Statement( + feature = ResultSubSetFeature.class, + sql = "SELECT FP.*, FS.*, F.*, CP.prefix coursePrefix, C.* " + + "FROM ~g~.Course C " + + " LEFT JOIN ~g~.CoursePrefix CP ON (C.prefixID=CP.prefixID) " + + " INNER JOIN ~p~.Facility F ON (C.facilityID=F.facilityID) " + + " LEFT JOIN ~p~.FacilityPrefix FP ON (F.prefixID=FP.prefixID) " + + " LEFT JOIN ~p~.FacilitySuffix FS ON (F.suffixID=FS.suffixID) " + + "WHERE course LIKE ? OR facility LIKE ?" + ) + private StatementProvider sqlSelectByName; + + @Override + public SubList findByJurisdiction(String country, String state, int page, int perPage) { + try { + FlexPreparedStatement fps = this.sqlSelectByJurisdiction.buildPreparedStatement(); + try { + fps.setVarchar(1, country); + fps.setVarchar(2, state); + fps.setSmallintU(3, (page - 1) * perPage); + fps.setSmallintU(4, perPage); + return (SubList) fps.executeQuery().getAllRows(); + } finally { + fps.close(); + } + } catch (SQLException se) { + throw new ServiceException(se); + } + } + + @Inject + @Named(Constants.STATEMENT_PROVIDER_GOLF) + @Statement( + feature = ResultSubSetFeature.class, + sql = "SELECT FP.*, FS.*, F.*, CP.prefix coursePrefix, C.* " + + "FROM ~g~.Course C " + + " LEFT JOIN ~g~.CoursePrefix CP ON (C.prefixID=CP.prefixID) " + + " INNER JOIN ~p~.Facility F ON (C.facilityID=F.facilityID) " + + " LEFT JOIN ~p~.FacilityPrefix FP ON (F.prefixID=FP.prefixID) " + + " LEFT JOIN ~p~.FacilitySuffix FS ON (F.suffixID=FS.suffixID) " + + "WHERE addrcountry=? AND addrstate=? AND C.deadline IS NULL AND F.deadline IS NULL " + ) + private StatementProvider sqlSelectByJurisdiction; + + @Override + public SubList findByLocation(double latitude, double longitude, int radiusInMiles, int page, + int perPage) { + double degrees = PostStatsSQL.miles2degrees(radiusInMiles); + + try { + FlexPreparedStatement fps = this.sqlSelectByGeolocation.buildPreparedStatement(); + try { + fps.setDouble(1, latitude - degrees); + fps.setDouble(2, latitude + degrees); + fps.setDouble(3, longitude - degrees); + fps.setDouble(4, longitude + degrees); + fps.setSmallintU(5, (page - 1) * perPage); + fps.setSmallintU(6, perPage); + return (SubList) fps.executeQuery().getAllRows(); + } finally { + fps.close(); + } + } catch (SQLException se) { + throw new ServiceException(se); + } + } + + @Inject + @Named(Constants.STATEMENT_PROVIDER_GOLF) + @Statement( + feature = ResultSubSetFeature.class, + sql = "SELECT FP.*, FS.*, F.*, CP.prefix coursePrefix, C.* " + + "FROM ~g~.Course C " + + " LEFT JOIN ~g~.CoursePrefix CP ON (C.prefixID=CP.prefixID) " + + " INNER JOIN ~p~.Facility F ON (C.facilityID=F.facilityID) " + + " LEFT JOIN ~p~.FacilityPrefix FP ON (F.prefixID=FP.prefixID) " + + " LEFT JOIN ~p~.FacilitySuffix FS ON (F.suffixID=FS.suffixID) " + + "WHERE F.latitude>=? AND F.latitude<=? AND F.longitude>=? AND F.longitude<=? " + + " AND C.deadline IS NULL AND F.deadline IS NULL " + ) + private StatementProvider sqlSelectByGeolocation; + + @Override + protected long getCacheExpirationInSeconds() { + return this.defaultCacheExpirationInSeconds; + } + + @Override + protected DataSet fetchOne(Integer courseId) throws SQLException { + FlexPreparedStatement fps = this.sqlSelectCourse.buildPreparedStatement(); + try { + fps.setSmallintU(1, courseId); + return fps.executeQuery().getNextRow(); + } finally { + fps.close(); + } + } + + @Inject + @Named(Constants.STATEMENT_PROVIDER_GOLF) + @Statement( + sql = "SELECT FP.*, FS.*, F.*, CP.prefix coursePrefix, C.* " + + "FROM ~g~.Course C " + + " LEFT JOIN ~g~.CoursePrefix CP ON (C.prefixID=CP.prefixID) " + + " INNER JOIN ~p~.Facility F ON (C.facilityID=F.facilityID) " + + " LEFT JOIN ~p~.FacilityPrefix FP ON (F.prefixID=FP.prefixID) " + + " LEFT JOIN ~p~.FacilitySuffix FS ON (F.suffixID=FS.suffixID) " + + "WHERE courseID=? " + ) + private StatementProvider sqlSelectCourse; + + @Override + protected Map fetchBulk(Collection courseIds) throws SQLException { + FlexPreparedStatement fps = this.sqlSelectCourses.buildPreparedStatement(courseIds); + try { + return fps.executeQuery().getAllRows("courseID", Integer.class); + } finally { + fps.close(); + } + } + + @Inject + @Named(Constants.STATEMENT_PROVIDER_POSTSTATS) + @Statement( + sql = "SELECT FP.*, FS.*, F.*, CP.prefix coursePrefix, C.* " + + "FROM ~g~.Course C " + + " LEFT JOIN ~g~.CoursePrefix CP ON (C.prefixID=CP.prefixID) " + + " INNER JOIN ~p~.Facility F ON (C.facilityID=F.facilityID) " + + " LEFT JOIN ~p~.FacilityPrefix FP ON (F.prefixID=FP.prefixID) " + + " LEFT JOIN ~p~.FacilitySuffix FS ON (F.suffixID=FS.suffixID) " + + "WHERE courseID IN (??) " + ) + private StatementProvider sqlSelectCourses; + +} diff --git a/src/main/java/com/poststats/golf/service/db/EventRoundServiceDAO.java b/src/main/java/com/poststats/golf/service/db/EventRoundServiceDAO.java new file mode 100644 index 0000000..217e774 --- /dev/null +++ b/src/main/java/com/poststats/golf/service/db/EventRoundServiceDAO.java @@ -0,0 +1,140 @@ +package com.poststats.golf.service.db; + +import com.brianlong.sql.DataSet; +import com.brianlong.sql.FlexPreparedStatement; +import com.brianlong.util.FlexMap; +import com.poststats.golf.api.Constants; +import com.poststats.golf.service.CourseService; +import com.poststats.golf.service.EventRoundService; +import com.poststats.provider.Statement; +import com.poststats.provider.StatementProvider; +import com.poststats.service.ServiceException; +import com.poststats.service.db.CacheableServiceDAO; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import java.sql.SQLException; +import java.util.Collection; +import java.util.Map; + +@ApplicationScoped +public class EventRoundServiceDAO extends CacheableServiceDAO implements EventRoundService { + + private final long defaultCacheExpirationInSeconds = 3600; + + @Inject + private CourseService courseService; + + @Override + public FlexMap get(long eroundId) { + return this.get(Long.valueOf(eroundId)); + } + + @Override + public FlexMap get(Long eventId) { + FlexMap row = super.get(Long.valueOf(eventId)); + if (row == null) + return null; + row.put("course", this.courseService.get(row.getInteger("courseID"))); + + return row; + } + + @Override + public FlexMap get(long eventId, short number) { + try { + FlexPreparedStatement fps = this.sqlSelectByNumber.buildPreparedStatement(); + try { + fps.setIntegerU(1, eventId); + fps.setTinyintU(2, number); + return fps.executeQuery().getNextRow(); + } finally { + fps.close(); + } + } catch (SQLException se) { + throw new ServiceException(se); + } + } + + @Inject + @Named(Constants.STATEMENT_PROVIDER_GOLF) + @Statement( + sql = "SELECT TF.*, ER.* " + + "FROM ~g~.EventRound ER " + + " LEFT JOIN ~g~.TeeFormat TF ON (ER.teeFormatID=TF.teeFormatID) " + + "WHERE ER.eventID=? AND ER.round=? " + ) + private StatementProvider sqlSelectByNumber; + + @Override + public Map getByEventId(long eventId) { + try { + FlexPreparedStatement fps = this.sqlSelectByEventId.buildPreparedStatement(); + try { + fps.setIntegerU(1, eventId); + return fps.executeQuery().getAllRows("eroundID", Long.class); + } finally { + fps.close(); + } + } catch (SQLException se) { + throw new ServiceException(se); + } + } + + @Inject + @Named(Constants.STATEMENT_PROVIDER_GOLF) + @Statement( + sql = "SELECT TF.*, ER.* " + + "FROM ~g~.EventRound ER " + + " LEFT JOIN ~g~.TeeFormat TF ON (ER.teeFormatID=TF.teeFormatID) " + + "WHERE ER.eventID=? " + ) + private StatementProvider sqlSelectByEventId; + + @Override + protected long getCacheExpirationInSeconds() { + return this.defaultCacheExpirationInSeconds; + } + + @Override + protected DataSet fetchOne(Long eroundId) throws SQLException { + FlexPreparedStatement fps = this.sqlSelect.buildPreparedStatement(); + try { + fps.setIntegerU(1, eroundId); + return fps.executeQuery().getNextRow(); + } finally { + fps.close(); + } + } + + @Inject + @Named(Constants.STATEMENT_PROVIDER_GOLF) + @Statement( + sql = "SELECT TF.*, ER.* " + + "FROM ~g~.EventRound ER " + + " LEFT JOIN ~g~.TeeFormat TF ON (ER.teeFormatID=TF.teeFormatID) " + + "WHERE ER.eroundID=? " + ) + private StatementProvider sqlSelect; + + @Override + protected Map fetchBulk(Collection eroundIds) throws SQLException { + FlexPreparedStatement fps = this.sqlSelectManys.buildPreparedStatement(eroundIds); + try { + return fps.executeQuery().getAllRows("eroundID", Long.class); + } finally { + fps.close(); + } + } + + @Inject + @Named(Constants.STATEMENT_PROVIDER_GOLF) + @Statement( + sql = "SELECT TF.*, ER.* " + + "FROM ~g~.EventRound ER " + + " LEFT JOIN ~g~.TeeFormat TF ON (ER.teeFormatID=TF.teeFormatID) " + + "WHERE ER.eroundID IN (??) " + ) + private StatementProvider sqlSelectManys; + +} diff --git a/src/main/java/com/poststats/golf/service/db/EventServiceDAO.java b/src/main/java/com/poststats/golf/service/db/EventServiceDAO.java index c8a2d6f..dfc0d56 100644 --- a/src/main/java/com/poststats/golf/service/db/EventServiceDAO.java +++ b/src/main/java/com/poststats/golf/service/db/EventServiceDAO.java @@ -5,6 +5,7 @@ import com.brianlong.sql.FlexPreparedStatement; import com.brianlong.util.FlexMap; import com.poststats.golf.api.Constants; import com.poststats.golf.service.EventService; +import com.poststats.golf.service.SeriesService; import com.poststats.provider.Statement; import com.poststats.provider.StatementProvider; import com.poststats.service.ServiceException; @@ -16,6 +17,7 @@ import java.sql.SQLException; import java.util.Collection; import java.util.HashSet; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; @ApplicationScoped @@ -23,11 +25,39 @@ public class EventServiceDAO extends CacheableServiceDAO implements EventS private final long defaultCacheExpirationInSeconds = 3600; + @Inject + private SeriesService seriesService; + @Override public FlexMap get(long eventId) { return this.get(Long.valueOf(eventId)); } + @Override + public FlexMap get(Long eventId) { + FlexMap row = super.get(Long.valueOf(eventId)); + if (row == null) + return null; + row.put("series", this.seriesService.get(row.getInteger("seriesID"))); + + return row; + } + + @Override + public Map get(Collection eventIds) { + Map rows = super.get(eventIds); + + // bulk fetch series from cache/db + Set seriesIds = new HashSet<>(); + for (Entry row : rows.entrySet()) + seriesIds.add(row.getValue().getInteger("seriesId")); + + Map serieses = this.seriesService.get(seriesIds); + for (Entry row : rows.entrySet()) + row.getValue().put("series", serieses.get(row.getValue().getInteger("seriesID"))); + return rows; + } + @Override public Set getIdsBySeriesId(int seriesId) { try { @@ -65,7 +95,7 @@ public class EventServiceDAO extends CacheableServiceDAO implements EventS @Inject @Named(Constants.STATEMENT_PROVIDER_GOLF) - @Statement(sql = "SELECT * FROM ~g~.Event WHERE seriesID=? ORDER BY E.liveline DESC ") + @Statement(sql = "SELECT * FROM ~g~.Event WHERE seriesID=? ORDER BY liveline DESC ") private StatementProvider sqlSelectBySeriesId; @Override diff --git a/src/main/java/com/poststats/golf/service/db/PersonServiceDAO.java b/src/main/java/com/poststats/golf/service/db/PersonServiceDAO.java index dc78c7d..f39e226 100644 --- a/src/main/java/com/poststats/golf/service/db/PersonServiceDAO.java +++ b/src/main/java/com/poststats/golf/service/db/PersonServiceDAO.java @@ -66,6 +66,11 @@ public class PersonServiceDAO extends CacheableServiceDAO implements Perso @Override public FlexMap get(long personId) { + return this.get(Long.valueOf(personId)); + } + + @Override + public FlexMap get(Long personId) { FlexMap row = this.get(Long.valueOf(personId)); if (row == null) return null;