From e69b343f9935c7d5dba416f700e0b399b73fc56f Mon Sep 17 00:00:00 2001 From: Brian Long Date: Sat, 4 Feb 2023 11:42:11 -0500 Subject: [PATCH] major refactor; reformatting --- .../java/com/poststats/golf/api/EventApi.java | 22 +-- .../poststats/golf/api/EventFinanceApi.java | 30 ++- .../poststats/golf/api/EventPersonApi.java | 129 +++++++------ .../com/poststats/golf/api/GolferApi.java | 7 +- .../com/poststats/golf/api/SeriesApi.java | 30 ++- .../security/EventPersonSecurityContext.java | 4 +- .../golf/service/EventPersonService.java | 6 +- .../poststats/golf/service/EventService.java | 25 ++- .../poststats/golf/service/PersonService.java | 45 ++++- .../poststats/golf/service/SeriesService.java | 28 ++- .../golf/service/db/CacheableServiceDAO.java | 18 ++ .../service/db/EventFinanceServiceDAO.java | 17 +- .../service/db/EventPersonServiceDAO.java | 137 +++++++++----- .../golf/service/db/EventServiceDAO.java | 177 ++++++++---------- .../golf/service/db/PersonServiceDAO.java | 138 +++++++++----- .../golf/service/db/SeriesServiceDAO.java | 69 ++++--- .../poststats/golf/servlet/EventFilter.java | 23 +-- .../poststats/golf/servlet/PersonFilter.java | 40 +--- .../poststats/golf/servlet/SeriesFilter.java | 17 +- .../golf/transformer/EventTransformer.java | 25 ++- .../golf/transformer/GolferTransformer.java | 13 +- .../golf/transformer/SeriesTransformer.java | 13 +- 22 files changed, 564 insertions(+), 449 deletions(-) create mode 100644 src/main/java/com/poststats/golf/service/db/CacheableServiceDAO.java diff --git a/src/main/java/com/poststats/golf/api/EventApi.java b/src/main/java/com/poststats/golf/api/EventApi.java index 5711324..cb51318 100644 --- a/src/main/java/com/poststats/golf/api/EventApi.java +++ b/src/main/java/com/poststats/golf/api/EventApi.java @@ -1,16 +1,13 @@ package com.poststats.golf.api; -import com.brianlong.sql.DataSet; +import com.brianlong.util.FlexMap; import com.fasterxml.jackson.core.JsonProcessingException; import com.poststats.api.Constants; import com.poststats.golf.api.model.Event; import com.poststats.golf.api.model.EventDetail; import com.poststats.golf.service.EventService; import com.poststats.golf.transformer.EventTransformer; -import io.swagger.v3.oas.annotations.OpenAPIDefinition; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.info.Contact; -import io.swagger.v3.oas.annotations.info.Info; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; @@ -32,13 +29,6 @@ import org.slf4j.LoggerFactory; @RequestScoped @Path("/golf/event/{eventId}") @Tag(name = "Event API") -@OpenAPIDefinition( - info = @Info( - contact = @Contact(name = "Brian Long", email = "brian.long@poststats.com"), - title = "PostStats Golf API", - description = "An API providing access to PostStats Golf objects." - ) -) public class EventApi { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @@ -68,8 +58,9 @@ public class EventApi { @ApiResponse(responseCode = "404", description = "An event with the specified ID could not be found") }) public Event get() throws JsonProcessingException { - DataSet row = this.eventService.get(this.eventId); - if (row == null) throw new WebApplicationException("Event not found", Status.NOT_FOUND); + FlexMap row = this.eventService.get(this.eventId); + if (row == null) + throw new WebApplicationException("Event not found", Status.NOT_FOUND); return this.eventTransformer.toModel(row); } @@ -86,8 +77,9 @@ public class EventApi { @ApiResponse(responseCode = "404", description = "An event with the specified ID could not be found") }) public EventDetail getDetail() throws JsonProcessingException { - DataSet row = this.eventService.getDetail(this.eventId); - if (row == null) throw new WebApplicationException("Event not found", Status.NOT_FOUND); + FlexMap row = this.eventService.get(this.eventId); + if (row == null) + throw new WebApplicationException("Event not found", Status.NOT_FOUND); return (EventDetail) this.eventTransformer.toModel(row, new EventDetail()); } diff --git a/src/main/java/com/poststats/golf/api/EventFinanceApi.java b/src/main/java/com/poststats/golf/api/EventFinanceApi.java index ad9f239..bc13d37 100644 --- a/src/main/java/com/poststats/golf/api/EventFinanceApi.java +++ b/src/main/java/com/poststats/golf/api/EventFinanceApi.java @@ -4,7 +4,6 @@ import com.brianlong.sql.DataSet; import com.fasterxml.jackson.core.JsonProcessingException; import com.poststats.golf.service.EventFinanceService; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; @@ -42,15 +41,17 @@ public class EventFinanceApi { @GET @Path("/balance/persons") - @RolesAllowed(Constants.EVENT_ROLE_PREFIX + "finance") + @RolesAllowed(Constants.EVENT_ROLE_PREFIX + "member") @Produces(Constants.V1_JSON) @Operation(summary = "Retrieves the balances of all participants in an event.") @ApiResponses({ - @ApiResponse(responseCode = "200", description = "Success", content = { - @Content(mediaType = Constants.V1_JSON), @Content(mediaType = "text/csv") - }), @ApiResponse(responseCode = "404", description = "An event with the specified ID could not be found") + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "401", description = "Not authenticated"), + @ApiResponse(responseCode = "403", description = "Authenticated, but not permitted"), + @ApiResponse(responseCode = "404", description = "An event with the specified ID could not be found") }) - public List> getBalanceByPersonsAsJson(@Context SecurityContext securityContext) throws JsonProcessingException { + public List> getBalanceByPersonsAsJson(@Context SecurityContext securityContext) + throws JsonProcessingException { List personsBalances = this.eventFinanceService.getPersonsBalances(this.eventId); List> personsBalancesJson = new ArrayList<>(personsBalances.size()); @@ -67,9 +68,16 @@ public class EventFinanceApi { } @GET - @Path("/balance/persons") + @Path("/balance/persons/csv") @RolesAllowed(Constants.EVENT_ROLE_PREFIX + "finance") @Produces("text/csv") + @Operation(summary = "Retrieves the balances of all participants in an event.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "401", description = "Not authenticated"), + @ApiResponse(responseCode = "403", description = "Authenticated, but not permitted"), + @ApiResponse(responseCode = "404", description = "An event with the specified ID could not be found") + }) public StreamingOutput getBalanceByPersonsAsCsv(@Context SecurityContext securityContext) throws IOException { List personsBalances = this.eventFinanceService.getPersonsBalances(this.eventId); @@ -79,11 +87,13 @@ public class EventFinanceApi { PrintStream pstream = new PrintStream(output); CSVPrinter personsBalancesCsvPrinter = new CSVPrinter(pstream, CSVFormat.DEFAULT); try { - personsBalancesCsvPrinter.printRecord("personID", "lname", "fname", "suffix", "expense", "paid", "balance"); + personsBalancesCsvPrinter.printRecord("personID", "lname", "fname", "suffix", "expense", "paid", + "balance"); for (DataSet personBalance : personsBalances) { - personsBalancesCsvPrinter.printRecord(personBalance.getLong("personID"), personBalance.getString("lname"), - personBalance.getString("fname"), personBalance.getString("suffix"), personBalance.getFloat("expense"), + personsBalancesCsvPrinter.printRecord(personBalance.getLong("personID"), + personBalance.getString("lname"), personBalance.getString("fname"), + personBalance.getString("suffix"), personBalance.getFloat("expense"), personBalance.getFloat("paid"), personBalance.getFloat("balance")); } diff --git a/src/main/java/com/poststats/golf/api/EventPersonApi.java b/src/main/java/com/poststats/golf/api/EventPersonApi.java index 6368c4b..29c0414 100644 --- a/src/main/java/com/poststats/golf/api/EventPersonApi.java +++ b/src/main/java/com/poststats/golf/api/EventPersonApi.java @@ -1,8 +1,7 @@ package com.poststats.golf.api; -import com.brianlong.sql.DataSet; +import com.brianlong.util.FlexMap; import com.fasterxml.jackson.core.JsonProcessingException; -import com.poststats.api.Constants; import com.poststats.api.model.Person; import com.poststats.golf.service.EventPersonService; import com.poststats.golf.service.EventService; @@ -18,15 +17,10 @@ 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.Context; -import jakarta.ws.rs.core.SecurityContext; import jakarta.ws.rs.core.StreamingOutput; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; -import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -53,118 +47,121 @@ public class EventPersonApi { @GET @Path("/people") - @RolesAllowed("member") + @RolesAllowed(Constants.EVENT_ROLE_PREFIX + "member") @Produces(Constants.V1_JSON) - @Operation(summary = "Retrieves the administrators and participants in an event.") + @Operation(summary = "Retrieves limited meta-data about all the participants and administrators in an event.") @ApiResponses({ @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "401", description = "Not authenticated"), + @ApiResponse(responseCode = "403", description = "Authenticated, but not permitted"), @ApiResponse(responseCode = "404", description = "An event with the specified ID could not be found") }) - public List get(@Context SecurityContext securityContext, @QueryParam("format") String format) throws JsonProcessingException, IOException { - if (!securityContext.isUserInRole(this.eventId + "~member")) throw new SecurityException("Not permitted"); - - List persons = this.eventPersonService.getPeople(this.eventId); - return this.toAddressBookAsJson(persons); + public List get() { + return this.personTransformer.toModel(this.eventPersonService.getPeople(this.eventId, false)); } @GET - @Path("/people") - @RolesAllowed("member") - @Produces("text/csv") - @Operation(summary = "Retrieves the administrators and participants in an event.") + @Path("/people/detail") + @RolesAllowed(Constants.EVENT_ROLE_PREFIX + "member") + @Produces(Constants.V1_JSON) + @Operation(summary = "Retrieves detailed meta-data about all the participants and administrators in an event.") @ApiResponses({ @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "401", description = "Not authenticated"), + @ApiResponse(responseCode = "403", description = "Authenticated, but not permitted"), @ApiResponse(responseCode = "404", description = "An event with the specified ID could not be found") }) - public StreamingOutput getAsCsv(@Context SecurityContext securityContext, @QueryParam("format") String format) throws JsonProcessingException, IOException { - if (!securityContext.isUserInRole(this.eventId + "~member")) throw new SecurityException("Not permitted"); + public List getDetail() { + return this.personTransformer.toModel(this.eventPersonService.getPeople(this.eventId, true)); + } - List persons = this.eventPersonService.getPeople(this.eventId); - return this.toAddressBookAsCsv(persons); + @GET + @Path("/people/csv") + @RolesAllowed(Constants.EVENT_ROLE_PREFIX + "membership") + @Produces("text/csv") + @Operation(summary = "Retrieves address book meta-data about all the participants and administrators in an event.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "401", description = "Not authenticated"), + @ApiResponse(responseCode = "403", description = "Authenticated, but not permitted"), + @ApiResponse(responseCode = "404", description = "An event with the specified ID could not be found") + }) + public StreamingOutput getAsCsv() { + List persons = this.eventPersonService.getPeople(this.eventId, true); + return this.toCsv(persons); } @GET @Path("/participants") - @RolesAllowed("member") @Produces(Constants.V1_JSON) - @Operation(summary = "Retrieves the participants in an event.") + @Operation(summary = "Retrieves limited meta-data about all the participants in an event.") @ApiResponses({ @ApiResponse(responseCode = "200", description = "Success"), @ApiResponse(responseCode = "404", description = "An event with the specified ID could not be found") }) - public List getParticipants(@Context SecurityContext securityContext, @Context @QueryParam("format") String format) - throws JsonProcessingException, IOException { - if (!securityContext.isUserInRole(this.eventId + "~member")) throw new SecurityException("Not permitted"); - - List persons = this.eventPersonService.getParticipants(this.eventId); - return this.toAddressBookAsJson(persons); + public List getParticipants() { + return this.personTransformer.toModel(this.eventPersonService.getParticipants(this.eventId, false)); } @GET - @Path("/participants") - @RolesAllowed("member") + @Path("/participants/csv") + @RolesAllowed(Constants.EVENT_ROLE_PREFIX + "membership") @Produces("text/csv") - @Operation(summary = "Retrieves the participants in an event.") + @Operation(summary = "Retrieves address book meta-data about all the participants in an event.") @ApiResponses({ @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "401", description = "Not authenticated"), + @ApiResponse(responseCode = "403", description = "Authenticated, but not permitted"), @ApiResponse(responseCode = "404", description = "An event with the specified ID could not be found") }) - public StreamingOutput getParticipantsAsCsv(@Context SecurityContext securityContext, @Context @QueryParam("format") String format) - throws JsonProcessingException, IOException { - if (!securityContext.isUserInRole(this.eventId + "~member")) throw new SecurityException("Not permitted"); - - List persons = this.eventPersonService.getParticipants(this.eventId); - return this.toAddressBookAsCsv(persons); + public StreamingOutput getParticipantsAsCsv() { + List persons = this.eventPersonService.getParticipants(this.eventId, true); + return this.toCsv(persons); } @GET @Path("/series/participants") - @RolesAllowed("member") + @RolesAllowed(Constants.EVENT_ROLE_PREFIX + "membership") @Produces(Constants.V1_JSON) - @Operation(summary = "Retrieves all the participants in an event series.") + @Operation(summary = "Retrieves limited meta-data about all the participants in an event series.") @ApiResponses({ @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse(responseCode = "401", description = "Not authenticated"), + @ApiResponse(responseCode = "403", description = "Authenticated, but not permitted"), @ApiResponse(responseCode = "404", description = "An event with the specified ID could not be found") }) - public List getSeriesParticipants(@Context SecurityContext securityContext, @QueryParam("format") String format) - throws JsonProcessingException, IOException { - if (!securityContext.isUserInRole(this.eventId + "~member")) throw new SecurityException("Not permitted"); - + public List getSeriesParticipants() throws JsonProcessingException, IOException { int seriesId = this.eventService.getSeriesId(this.eventId); - - Person principal = (Person) securityContext.getUserPrincipal(); - Set eventIds = this.eventPersonService.getSeriesEventIdsAsParticipant(seriesId, principal.getId()); + Set eventIds = this.eventService.getIdsBySeriesId(seriesId); Set personIds = new HashSet<>(); - List persons = new LinkedList<>(); + List persons = new LinkedList<>(); for (long eventId : eventIds) { - List tmpPersons = this.eventPersonService.getParticipants(eventId); - for (DataSet person : tmpPersons) - if (personIds.add(person.getLong("personID"))) persons.add(person); + List tmpPersons = this.eventPersonService.getParticipants(eventId, false); + for (FlexMap person : tmpPersons) + if (personIds.add(person.getLong(com.poststats.sql.Constants.PERSON_ID))) + persons.add(person); } - return this.toAddressBookAsJson(persons); + return this.personTransformer.toModel(persons); } - private List toAddressBookAsJson(List persons) throws JsonProcessingException { - List personsJson = new ArrayList<>(persons.size()); - for (DataSet person : persons) - personsJson.add(this.personTransformer.toModel(person)); - return personsJson; - } - - private StreamingOutput toAddressBookAsCsv(List persons) throws IOException { + private StreamingOutput toCsv(List persons) { return new StreamingOutput() { @Override - public void write(OutputStream output) throws IOException, WebApplicationException { + public void write(OutputStream output) throws IOException { PrintStream pstream = new PrintStream(output); CSVPrinter personsCsvPrinter = new CSVPrinter(pstream, CSVFormat.DEFAULT); try { - personsCsvPrinter.printRecord("ID", "Prefix", "Last Name", "First Name", "Suffix", "Email Address"); + personsCsvPrinter.printRecord("ID", "Prefix", "Last Name", "First Name", "Suffix", "Email Address", + "Mobile Phone", "Address Lines", "City", "State", "Country", "Postal Code"); - for (DataSet person : persons) { - personsCsvPrinter.printRecord(person.getLong("personID"), person.getString("prefix"), person.getString("lname"), - person.getString("fname"), person.getString("suffix"), person.getString("email")); + for (FlexMap person : persons) { + personsCsvPrinter.printRecord(person.getLong("personID"), person.getString("prefix"), + person.getString("lname"), person.getString("fname"), person.getString("suffix"), + person.getString("email"), person.getString("cellphone"), person.getString("addrlines"), + person.getString("addrcity"), person.getString("addrstate"), + person.getString("addrcountry"), person.getString("addrzip")); } personsCsvPrinter.flush(); diff --git a/src/main/java/com/poststats/golf/api/GolferApi.java b/src/main/java/com/poststats/golf/api/GolferApi.java index db647a2..6b176f6 100644 --- a/src/main/java/com/poststats/golf/api/GolferApi.java +++ b/src/main/java/com/poststats/golf/api/GolferApi.java @@ -1,6 +1,6 @@ package com.poststats.golf.api; -import com.brianlong.sql.DataSet; +import com.brianlong.util.FlexMap; import com.fasterxml.jackson.core.JsonProcessingException; import com.poststats.api.Constants; import com.poststats.api.model.Person; @@ -57,8 +57,9 @@ public class GolferApi { @ApiResponse(responseCode = "404", description = "A golfer with the specified ID could not be found") }) public Person get() throws JsonProcessingException { - DataSet row = this.personService.get(this.personId); - if (row == null) throw new WebApplicationException("Event not found", Status.NOT_FOUND); + FlexMap row = this.personService.get(this.personId); + if (row == null) + throw new WebApplicationException("Event not found", Status.NOT_FOUND); return this.golferTransformer.toModel(row); } diff --git a/src/main/java/com/poststats/golf/api/SeriesApi.java b/src/main/java/com/poststats/golf/api/SeriesApi.java index 1099f5a..544c5d2 100644 --- a/src/main/java/com/poststats/golf/api/SeriesApi.java +++ b/src/main/java/com/poststats/golf/api/SeriesApi.java @@ -1,6 +1,6 @@ package com.poststats.golf.api; -import com.brianlong.sql.DataSet; +import com.brianlong.util.FlexMap; import com.fasterxml.jackson.core.JsonProcessingException; import com.poststats.api.Constants; import com.poststats.golf.api.model.Event; @@ -9,7 +9,10 @@ import com.poststats.golf.service.EventService; import com.poststats.golf.service.SeriesService; import com.poststats.golf.transformer.EventTransformer; import com.poststats.golf.transformer.SeriesTransformer; +import io.swagger.v3.oas.annotations.OpenAPIDefinition; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.info.Contact; +import io.swagger.v3.oas.annotations.info.Info; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; @@ -36,6 +39,13 @@ import org.slf4j.LoggerFactory; @RequestScoped @Path("/golf/series/{seriesId}") @Tag(name = "Event Series API") +@OpenAPIDefinition( + info = @Info( + contact = @Contact(name = "Brian Long", email = "brian.long@poststats.com"), + title = "PostStats Golf API", + description = "An API providing access to PostStats Golf objects." + ) +) public class SeriesApi { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @@ -71,8 +81,9 @@ public class SeriesApi { @ApiResponse(responseCode = "404", description = "An event series with the specified ID could not be found") }) public Series get() throws JsonProcessingException { - DataSet row = this.seriesService.get(this.seriesId); - if (row == null) throw new WebApplicationException("Series not found", Status.NOT_FOUND); + FlexMap row = this.seriesService.get(this.seriesId); + if (row == null) + throw new WebApplicationException("Series not found", Status.NOT_FOUND); return this.seriesTransformer.toModel(row); } @@ -86,8 +97,9 @@ public class SeriesApi { @ApiResponse(responseCode = "404", description = "An event series with the specified ID could not be found") }) public Set getEventIds() throws JsonProcessingException { - Set eventIds = this.eventService.getIds(this.seriesId); - if (eventIds.isEmpty()) throw new WebApplicationException("Series or events not found", Status.NOT_FOUND); + Set eventIds = this.eventService.getIdsBySeriesId(this.seriesId); + if (eventIds.isEmpty()) + throw new WebApplicationException("Series or events not found", Status.NOT_FOUND); return eventIds; } @@ -103,11 +115,13 @@ public class SeriesApi { @ApiResponse(responseCode = "404", description = "An event series with the specified ID could not be found") }) public List getEvents(@QueryParam("reverse") Boolean reverse) throws JsonProcessingException { - Map rows = this.eventService.get(this.seriesId, !Boolean.TRUE.equals(reverse)); - if (rows.isEmpty()) throw new WebApplicationException("Series or events not found", Status.NOT_FOUND); + Map rows = this.eventService.getBySeriesId(this.seriesId, + !Boolean.TRUE.equals(reverse)); + if (rows.isEmpty()) + throw new WebApplicationException("Series or events not found", Status.NOT_FOUND); List events = new LinkedList(); - for (DataSet row : rows.values()) { + for (FlexMap row : rows.values()) { events.add(this.eventTransformer.toModel(row)); } diff --git a/src/main/java/com/poststats/golf/security/EventPersonSecurityContext.java b/src/main/java/com/poststats/golf/security/EventPersonSecurityContext.java index 8dedc49..3a3403d 100644 --- a/src/main/java/com/poststats/golf/security/EventPersonSecurityContext.java +++ b/src/main/java/com/poststats/golf/security/EventPersonSecurityContext.java @@ -24,8 +24,8 @@ public class EventPersonSecurityContext implements SecurityContext { @Override public boolean isUserInRole(String role) { - this.logger.trace("Checking if user {} in is in role {} for event {}", this.securityContext.getUserPrincipal() - .getName(), role, this.eventId); + this.logger.trace("Checking if user {} in is in role {} for event {}", + this.securityContext.getUserPrincipal().getName(), role, this.eventId); Person person = (Person) this.securityContext.getUserPrincipal(); if (person == null) { return false; diff --git a/src/main/java/com/poststats/golf/service/EventPersonService.java b/src/main/java/com/poststats/golf/service/EventPersonService.java index 2df6d40..2f4f496 100644 --- a/src/main/java/com/poststats/golf/service/EventPersonService.java +++ b/src/main/java/com/poststats/golf/service/EventPersonService.java @@ -1,14 +1,14 @@ package com.poststats.golf.service; -import com.brianlong.sql.DataSet; +import com.brianlong.util.FlexMap; import java.util.List; import java.util.Set; public interface EventPersonService { - List getPeople(long eventId); + List getPeople(long eventId, boolean fullDetails); - List getParticipants(long eventId); + List getParticipants(long eventId, boolean fullDetails); Set getSeriesEventIdsAsParticipant(int seriesId, long personId); diff --git a/src/main/java/com/poststats/golf/service/EventService.java b/src/main/java/com/poststats/golf/service/EventService.java index fc1e4f4..0b99cd1 100644 --- a/src/main/java/com/poststats/golf/service/EventService.java +++ b/src/main/java/com/poststats/golf/service/EventService.java @@ -1,19 +1,32 @@ package com.poststats.golf.service; -import com.brianlong.sql.DataSet; +import com.brianlong.util.FlexMap; +import java.util.Collection; import java.util.Map; import java.util.Set; public interface EventService { - DataSet get(long eventId); + /** + * This method retrieves meta-data about the specified event. + * + * @param eventId A unique identifier for the event. + * @return A map of meta-data specific to the event. + */ + FlexMap get(long eventId); - DataSet getDetail(long eventId); + /** + * This method retrieves meta-data about the specified event. + * + * @param eventIds Unique identifiers for the event. + * @return A map of unique identifiers to meta-data specific to each event. + */ + Map get(Collection eventIds); - Map get(int seriesId, boolean chronological); + Set getIdsBySeriesId(int seriesId); - Set getIds(int seriesId); + Map getBySeriesId(int seriesId, boolean chronological); - int getSeriesId(long eventId); + Integer getSeriesId(long eventId); } diff --git a/src/main/java/com/poststats/golf/service/PersonService.java b/src/main/java/com/poststats/golf/service/PersonService.java index b6c7e20..5c19c25 100644 --- a/src/main/java/com/poststats/golf/service/PersonService.java +++ b/src/main/java/com/poststats/golf/service/PersonService.java @@ -1,12 +1,51 @@ package com.poststats.golf.service; -import com.brianlong.sql.DataSet; +import com.brianlong.util.FlexMap; import com.poststats.golf.security.Person; +import java.util.Collection; +import java.util.Map; public interface PersonService { - Person buildUserPrincipal(com.poststats.security.Person person); + /** + * This method builds a `UserPrincipal` object about the golfer. + * + * Unlike the non-golf `PersonService`, this will include golfer and event + * roles. + * + * @param person A non-golf `UserPrincipal` object. + * @return A golfer `UserPrincipal` object. + */ + Person getUserPrincipal(com.poststats.security.Person person); - DataSet get(long personId); + /** + * This method retrieves meta-data about the specified golfer. + * + * If you want meta-data information about only the person, use the non-golf + * `PersonService`. + * + * This retrieves everything from the non-golf `PersonService` and supplements + * it with simple golfer meta-data, like stroke handicaps. It does not include + * related data like point handicap or round counts. + * + * @param personId A unique identifier for the golfer. + * @return A map of meta-data specific to the golfer. + */ + FlexMap get(long personId); + + /** + * This method retrieves meta-data about the specified golfers. + * + * If you want meta-data information about only the person, use the non-golf + * `PersonService`. + * + * This retrieves everything from the non-golf `PersonService` and supplements + * it with simple golfer meta-data, like stroke handicaps. It does not include + * related data like point handicap or round counts. + * + * @param personIds Unique identifiers for the golfers. + * @return A map of unique identifiers to meta-data specific to each golfer. + */ + Map get(Collection personIds); } diff --git a/src/main/java/com/poststats/golf/service/SeriesService.java b/src/main/java/com/poststats/golf/service/SeriesService.java index 4104498..e1dc4e3 100644 --- a/src/main/java/com/poststats/golf/service/SeriesService.java +++ b/src/main/java/com/poststats/golf/service/SeriesService.java @@ -1,11 +1,33 @@ package com.poststats.golf.service; -import com.brianlong.sql.DataSet; +import com.brianlong.util.FlexMap; +import java.util.Collection; +import java.util.Map; public interface SeriesService { - DataSet get(int seriesId); + /** + * This method retrieves meta-data about the specified series. + * + * @param seriesId A unique identifier for the series. + * @return A map of meta-data specific to the series. + */ + FlexMap get(int seriesId); - DataSet getByEventId(long eventId); + /** + * This method retrieves meta-data about the specified series. + * + * @param seriesIds Unique identifiers for the series. + * @return A map of unique identifiers to meta-data specific to each series. + */ + Map get(Collection seriesIds); + + /** + * This method retrieves series meta-data about the specified event. + * + * @param eventId A unique identifier for the event. + * @return A map of meta-data specific to the series. + */ + FlexMap getByEventId(long eventId); } diff --git a/src/main/java/com/poststats/golf/service/db/CacheableServiceDAO.java b/src/main/java/com/poststats/golf/service/db/CacheableServiceDAO.java new file mode 100644 index 0000000..fbcd9f2 --- /dev/null +++ b/src/main/java/com/poststats/golf/service/db/CacheableServiceDAO.java @@ -0,0 +1,18 @@ +package com.poststats.golf.service.db; + +import com.poststats.golf.sql.GolfDataSource; +import java.sql.Connection; + +public abstract class CacheableServiceDAO extends com.poststats.service.db.CacheableServiceDAO { + + @Override + protected Connection acquireConnection() { + return GolfDataSource.getInstance().acquire(true); + } + + @Override + protected void releaseConnection(Connection dbcon) { + GolfDataSource.getInstance().release(dbcon); + } + +} diff --git a/src/main/java/com/poststats/golf/service/db/EventFinanceServiceDAO.java b/src/main/java/com/poststats/golf/service/db/EventFinanceServiceDAO.java index 12abbb4..0c26112 100644 --- a/src/main/java/com/poststats/golf/service/db/EventFinanceServiceDAO.java +++ b/src/main/java/com/poststats/golf/service/db/EventFinanceServiceDAO.java @@ -6,7 +6,6 @@ import com.poststats.golf.service.EventFinanceService; import com.poststats.golf.sql.GolfSQL; import com.poststats.service.ServiceException; import com.poststats.sql.PostStatsDataSource; -import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.ApplicationScoped; import java.sql.Connection; import java.sql.SQLException; @@ -15,22 +14,15 @@ import java.util.List; @ApplicationScoped public class EventFinanceServiceDAO implements EventFinanceService { - @PostConstruct - public void init() { - System.out.println("EventFinanceServiceDAO init"); - } - @Override public List getPersonsBalances(long eventId) { - Connection dbcon = PostStatsDataSource.getInstance() - .acquire(true); + Connection dbcon = PostStatsDataSource.getInstance().acquire(true); try { return this.queryPersonsBalances(dbcon, eventId); } catch (SQLException se) { throw new ServiceException(se); } finally { - PostStatsDataSource.getInstance() - .release(dbcon); + PostStatsDataSource.getInstance().release(dbcon); } } @@ -38,9 +30,8 @@ public class EventFinanceServiceDAO implements EventFinanceService { FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, SQL_SELECT_BALANCES); try { for (int i = 1; i <= 5; i++) - fps.setIntegerU(1, eventId); - return fps.executeQuery() - .getAllRows(); + fps.setIntegerU(i, eventId); + return fps.executeQuery().getAllRows(); } finally { fps.close(); } diff --git a/src/main/java/com/poststats/golf/service/db/EventPersonServiceDAO.java b/src/main/java/com/poststats/golf/service/db/EventPersonServiceDAO.java index b00f1f4..ed4b80c 100644 --- a/src/main/java/com/poststats/golf/service/db/EventPersonServiceDAO.java +++ b/src/main/java/com/poststats/golf/service/db/EventPersonServiceDAO.java @@ -1,97 +1,136 @@ package com.poststats.golf.service.db; +import com.brianlong.cache.CacheRetrievalException; import com.brianlong.sql.DataSet; +import com.brianlong.sql.FlexPreparedStatement; +import com.brianlong.util.FlexMap; +import com.poststats.golf.cache.GolferCache; import com.poststats.golf.service.EventPersonService; +import com.poststats.golf.sql.GolfSQL; import com.poststats.service.ServiceException; import com.poststats.sql.PostStatsDataSource; -import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.ApplicationScoped; -import jakarta.ws.rs.WebApplicationException; -import jakarta.ws.rs.core.Response.Status; +import java.math.BigInteger; import java.sql.Connection; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Set; @ApplicationScoped public class EventPersonServiceDAO implements EventPersonService { - @PostConstruct - public void init() { - System.out.println("EventFinanceServiceDAO init"); - } - @Override - public List getPeople(long eventId) { - Connection dbcon = PostStatsDataSource.getInstance() - .acquire(true); + public List getPeople(long eventId, boolean includeDetails) { + Connection dbcon = PostStatsDataSource.getInstance().acquire(true); try { - return this.queryPersons(dbcon, eventId); + return this.queryPersons(dbcon, eventId, includeDetails); + } catch (CacheRetrievalException cre) { + throw new ServiceException(cre); } catch (SQLException se) { throw new ServiceException(se); } finally { - PostStatsDataSource.getInstance() - .release(dbcon); + PostStatsDataSource.getInstance().release(dbcon); } } @Override - public List getParticipants(long eventId) { - Connection dbcon = PostStatsDataSource.getInstance() - .acquire(true); + public List getParticipants(long eventId, boolean includeDetails) { + Connection dbcon = PostStatsDataSource.getInstance().acquire(true); try { - return this.queryParticipants(dbcon, eventId); + return this.queryParticipants(dbcon, eventId, includeDetails); + } catch (CacheRetrievalException cre) { + throw new ServiceException(cre); } catch (SQLException se) { throw new ServiceException(se); } finally { - PostStatsDataSource.getInstance() - .release(dbcon); + PostStatsDataSource.getInstance().release(dbcon); } } @Override public Set getSeriesEventIdsAsParticipant(int seriesId, long personId) { - Connection dbcon = PostStatsDataSource.getInstance() - .acquire(true); + Connection dbcon = PostStatsDataSource.getInstance().acquire(true); try { return this.querySeriesEventIdsAsParticipant(dbcon, seriesId, personId); } catch (SQLException se) { throw new ServiceException(se); } finally { - PostStatsDataSource.getInstance() - .release(dbcon); + PostStatsDataSource.getInstance().release(dbcon); } } - private List queryPersons(Connection dbcon, long eventId) throws SQLException { - throw new WebApplicationException(Status.NOT_IMPLEMENTED); - /* - * FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, - * SQL_SELECT_PERSONS); try { fps.setIntegerU(1, eventId); return - * fps.executeQuery().getAllRows(); } finally { fps.close(); } - */ + private List queryPersons(Connection dbcon, long eventId, boolean includeDetails) + throws CacheRetrievalException, SQLException { + return this.query(dbcon, SQL_SELECT_PERSONS, eventId, 2, includeDetails); } - private List queryParticipants(Connection dbcon, long eventId) throws SQLException { - throw new WebApplicationException(Status.NOT_IMPLEMENTED); - /* - * FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, - * SQL_SELECT_PARTICIPANTS); try { fps.setIntegerU(1, eventId); return - * fps.executeQuery().getAllRows(); } finally { fps.close(); } - */ + private List queryParticipants(Connection dbcon, long eventId, boolean includeDetails) + throws CacheRetrievalException, SQLException { + return this.query(dbcon, SQL_SELECT_PARTICIPANTS, eventId, 1, includeDetails); } - private Set querySeriesEventIdsAsParticipant(Connection dbcon, int seriesId, long personId) throws SQLException { - throw new WebApplicationException(Status.NOT_IMPLEMENTED); - /* - * FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, - * SQL_SELECT_PARTICIPANTS); try { fps.setSmallintU(1, seriesId); - * fps.setIntegerU(2, personId); - * - * Set eventIds = new HashSet<>(); - * fps.executeQuery().getFirstColumn(Long.class, eventIds); return eventIds; } - * finally { fps.close(); } - */ + private List query(Connection dbcon, String sql, long eventId, int parameterCount, boolean includeDetails) + throws CacheRetrievalException, SQLException { + Map epersonIdMap; + + FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, sql); + try { + for (int i = 1; i <= parameterCount; i++) + fps.setIntegerU(i, eventId); + epersonIdMap = fps.executeQuery().getFirstTwoColumns(BigInteger.class, Long.class); + } finally { + fps.close(); + } + + Map personMap = GolferCache.getInstance().get(epersonIdMap.values()); + + List rows = new ArrayList<>(personMap.size()); + for (Entry eperson : epersonIdMap.entrySet()) { + FlexMap row = (FlexMap) personMap.get(eperson.getValue()).clone(); + row.put("epersonID", eperson.getKey()); + rows.add(row); + } + + return rows; } + private Set querySeriesEventIdsAsParticipant(Connection dbcon, int seriesId, long personId) + throws SQLException { + FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, SQL_SELECT_SERIES_PARTICIPANTS); + try { + fps.setSmallintU(1, seriesId); + fps.setIntegerU(2, personId); + + Set set = new HashSet<>(); + fps.executeQuery().getFirstColumn(Long.class, set); + return set; + } finally { + fps.close(); + } + } + + + + private static final String SQL_SELECT_PARTICIPANTS = GolfSQL.changeSchema( + "SELECT epersonID, personID FROM ~g~.EventPerson WHERE eventID=?"); + + private static final String SQL_SELECT_SERIES_PARTICIPANTS = GolfSQL.changeSchema( + "SELECT DISTINCT EP2.personID " + + "FROM ~g~.EventPerson EP1 " + + " INNER JOIN Event E ON (EP1.eventID=E.eventID) " + + " INNER JOIN EventPerson EP2 ON (E.eventID=EP2.eventID) " + + "WHERE EP1.personID=? AND E.seriesID=?"); + + private static final String SQL_SELECT_PERSONS = GolfSQL.changeSchema( + "SELECT epersonID, personID FROM ~g~.EventPerson WHERE eventID=? " + + "UNION " + + "SELECT DISTINCT NULL, personID " + + "FROM ~g~.EventPersonAccessControl EPAC " + + " LEFT JOIN ~g~.EventPerson EP ON (EPAC.eventID=EP.eventID AND EPAC.personID=EP.personID) " + + "WHERE eventID=? AND EP.personID IS NULL "); + } 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 cd31242..3841b9e 100644 --- a/src/main/java/com/poststats/golf/service/db/EventServiceDAO.java +++ b/src/main/java/com/poststats/golf/service/db/EventServiceDAO.java @@ -2,169 +2,142 @@ 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.service.EventService; import com.poststats.golf.sql.GolfDataSource; import com.poststats.golf.sql.GolfSQL; import com.poststats.service.ServiceException; -import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.ApplicationScoped; import java.sql.Connection; import java.sql.SQLException; +import java.util.Collection; import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.Set; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @ApplicationScoped -public class EventServiceDAO implements EventService { +public class EventServiceDAO extends CacheableServiceDAO implements EventService { - private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final long defaultCacheExpirationInSeconds = 3600; - @PostConstruct - public void init() { - this.logger.debug("EventFinanceServiceDAO init"); + @Override + public FlexMap get(long eventId) { + return this.get(Long.valueOf(eventId)); } @Override - public DataSet get(long eventId) { - Connection dbcon = GolfDataSource.getInstance() - .acquire(true); + public Map getBySeriesId(int seriesId, boolean chronological) { + List eventIds = new LinkedList<>(); + + Connection dbcon = GolfDataSource.getInstance().acquire(true); try { - return this.queryEvent(dbcon, eventId, false); + this.queryEventIds(dbcon, seriesId, chronological, eventIds); } catch (SQLException se) { throw new ServiceException(se); } finally { - GolfDataSource.getInstance() - .release(dbcon); + GolfDataSource.getInstance().release(dbcon); } + + return this.get(eventIds); } @Override - public DataSet getDetail(long eventId) { - Connection dbcon = GolfDataSource.getInstance() - .acquire(true); + public Set getIdsBySeriesId(int seriesId) { + Set eventIds = new HashSet<>(); + + Connection dbcon = GolfDataSource.getInstance().acquire(true); try { - return this.queryEvent(dbcon, eventId, true); + this.queryEventIds(dbcon, seriesId, null, eventIds); } catch (SQLException se) { throw new ServiceException(se); } finally { - GolfDataSource.getInstance() - .release(dbcon); + GolfDataSource.getInstance().release(dbcon); } + + return eventIds; } @Override - public Map get(int seriesId, boolean chronological) { - Connection dbcon = GolfDataSource.getInstance() - .acquire(true); - try { - return this.queryEvents(dbcon, seriesId, chronological); - } catch (SQLException se) { - throw new ServiceException(se); - } finally { - GolfDataSource.getInstance() - .release(dbcon); - } - } + public Integer getSeriesId(long eventId) { + FlexMap event = this.getIfHit(eventId); + if (event != null) + return event.getInteger("seriesID"); - @Override - public Set getIds(int seriesId) { - Connection dbcon = GolfDataSource.getInstance() - .acquire(true); - try { - return this.queryEventIds(dbcon, seriesId); - } catch (SQLException se) { - throw new ServiceException(se); - } finally { - GolfDataSource.getInstance() - .release(dbcon); - } - } - - @Override - public int getSeriesId(long eventId) { - Connection dbcon = GolfDataSource.getInstance() - .acquire(true); + Connection dbcon = GolfDataSource.getInstance().acquire(true); try { return this.querySeriesId(dbcon, eventId); } catch (SQLException se) { throw new ServiceException(se); } finally { - GolfDataSource.getInstance() - .release(dbcon); + GolfDataSource.getInstance().release(dbcon); } } - private static final String SQL_SELECT_EVENT_BRIEF = GolfSQL.changeSchema( - "SELECT E.* " - + "FROM ~g~.Event E " - + "WHERE E.eventID=? "); - private static final String SQL_SELECT_EVENT_DETAIL = GolfSQL.changeSchema( - "SELECT E.*, EF.* " - + "FROM ~g~.Event E " - + " INNER JOIN ~g~.EventFeature EF ON (E.eventID=EF.eventID) " - + "WHERE E.eventID=? "); + @Override + protected long getCacheExpirationInSeconds() { + return this.defaultCacheExpirationInSeconds; + } - private DataSet queryEvent(Connection dbcon, long eventId, boolean includeExtraDetail) throws SQLException { - FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, includeExtraDetail ? SQL_SELECT_EVENT_DETAIL : SQL_SELECT_EVENT_BRIEF); + @Override + protected DataSet fetchOne(Connection dbcon, Long eventId) throws SQLException { + String sql = SQL_SELECT + "WHERE E.eventID=?"; + FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, sql); try { fps.setIntegerU(1, eventId); - return fps.executeQuery() - .getNextRow(); + return fps.executeQuery().getNextRow(); + } finally { + fps.close(); + } + } + + @Override + protected Map fetchBulk(Connection dbcon, Collection eventIds) throws SQLException { + String sql = SQL_SELECT + "WHERE E.eventID IN (??)"; + FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, sql, eventIds); + try { + return fps.executeQuery().getAllRows("eventID", Long.class); + } finally { + fps.close(); + } + } + + private static final String SQL_SELECT = GolfSQL.changeSchema( + "SELECT E.*, EF.* " + + "FROM ~g~.Event E " + + " INNER JOIN ~g~.EventFeature EF ON (E.eventID=EF.eventID) "); + + private void queryEventIds(Connection dbcon, int seriesId, Boolean chronological, Collection eventIds) + throws SQLException { + String sql = SQL_SELECT_EVENTS; + if (chronological != null) + sql += chronological ? SQL_ORDER_EVENTS_ASC : SQL_ORDER_EVENTS_DESC; + FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, sql); + try { + fps.setSmallintU(1, seriesId); + fps.executeQuery().getFirstColumn(Long.class, eventIds); } finally { fps.close(); } } private static final String SQL_SELECT_EVENTS = GolfSQL.changeSchema( - "SELECT E.* " - + "FROM ~g~.Event E " - + "WHERE E.seriesID=? "); + "SELECT E.eventID FROM ~g~.Event E WHERE E.seriesID=? "); private static final String SQL_ORDER_EVENTS_ASC = "ORDER BY E.liveline ASC"; private static final String SQL_ORDER_EVENTS_DESC = "ORDER BY E.liveline DESC"; - private Map queryEvents(Connection dbcon, int seriesId, boolean chronological) throws SQLException { - FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, SQL_SELECT_EVENTS + (chronological ? SQL_ORDER_EVENTS_ASC : SQL_ORDER_EVENTS_DESC)); + private Integer querySeriesId(Connection dbcon, long eventId) throws SQLException { + FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, SQL_SELECT_SERIES_REF); try { - fps.setSmallintU(1, seriesId); - return fps.executeQuery() - .getAllRows("eventID", Long.class); + fps.setIntegerU(1, eventId); + return fps.executeQuery().getOne(Integer.class); } finally { fps.close(); } } - private static final String SQL_SELECT_EVENT_IDS = GolfSQL.changeSchema( - "SELECT eventID FROM ~g~.Event WHERE seriesID=? "); - - private Set queryEventIds(Connection dbcon, int seriesId) throws SQLException { - Set eventIds = new HashSet<>(32); - - FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, SQL_SELECT_EVENT_IDS); - try { - fps.setSmallintU(1, seriesId); - fps.executeQuery() - .getFirstColumn(Long.class, eventIds); - } finally { - fps.close(); - } - - return eventIds; - } - private static final String SQL_SELECT_SERIES_REF = GolfSQL.changeSchema( "SELECT seriesID FROM ~g~.Event WHERE eventID=?"); - private int querySeriesId(Connection dbcon, long eventId) throws SQLException { - FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, SQL_SELECT_SERIES_REF); - try { - fps.setIntegerU(1, eventId); - return fps.executeQuery() - .getOne(Integer.class); - } finally { - fps.close(); - } - } - } 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 fdbdb4d..ad6aa54 100644 --- a/src/main/java/com/poststats/golf/service/db/PersonServiceDAO.java +++ b/src/main/java/com/poststats/golf/service/db/PersonServiceDAO.java @@ -1,82 +1,127 @@ package com.poststats.golf.service.db; +import com.brianlong.cache.CacheException; +import com.brianlong.cache.CacheableFetcher; +import com.brianlong.cache.ClusterAwareMemoryCacher; import com.brianlong.sql.DataSet; import com.brianlong.sql.FlexPreparedStatement; +import com.brianlong.util.FlexMap; import com.poststats.golf.security.Person; import com.poststats.golf.service.PersonService; import com.poststats.golf.sql.GolfDataSource; import com.poststats.golf.sql.GolfSQL; import com.poststats.service.ServiceException; +import com.poststats.sql.Constants; +import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.ApplicationScoped; +import java.io.IOException; import java.sql.Connection; import java.sql.SQLException; +import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Set; @ApplicationScoped -public class PersonServiceDAO implements PersonService { +public class PersonServiceDAO extends CacheableServiceDAO implements PersonService { + + private final int roleCacheExpirationInSeconds = 300; + private final int infoCacheExpirationInSeconds = 120; + + private ClusterAwareMemoryCacher principalCacher; + + @PostConstruct + private void initCache() { + this.principalCacher = new ClusterAwareMemoryCacher(this.roleCacheExpirationInSeconds * 1000L, + this.clusterService.getHazelcastInstance()); + } @Override - public Person buildUserPrincipal(com.poststats.security.Person person) { - Connection dbcon = GolfDataSource.getInstance() - .acquire(true); + protected long getCacheExpirationInSeconds() { + return this.infoCacheExpirationInSeconds; + } + + @Override + public Person getUserPrincipal(com.poststats.security.Person person) { try { - return new Person(person, this.queryAccessControls(dbcon, person.getId()), this.queryEventAccessControls(dbcon, person.getId())); - } catch (SQLException se) { - throw new ServiceException(se); - } finally { - GolfDataSource.getInstance() - .release(dbcon); + return this.principalCacher.get(person.getId(), new PrincipalCacheableFetcher() { + @Override + public com.poststats.security.Person getPerson() { + return person; + } + }); + } catch (CacheException ce) { + throw new ServiceException(ce); } } @Override - public DataSet get(long personId) { - Connection dbcon = GolfDataSource.getInstance() - .acquire(true); - try { - return this.queryBasics(dbcon, personId); - } catch (SQLException se) { - throw new ServiceException(se); - } finally { - GolfDataSource.getInstance() - .release(dbcon); - } + public FlexMap get(long personId) { + return this.get(Long.valueOf(personId)); } - private DataSet queryBasics(Connection dbcon, long personId) throws SQLException { - FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, GolfSQL.changeSchema( - "SELECT * FROM ~g~.Person WHERE personID=?")); + private abstract class PrincipalCacheableFetcher implements CacheableFetcher { + + public abstract com.poststats.security.Person getPerson(); + + @Override + public Person get(Long personId) throws SQLException { + Connection dbcon = GolfDataSource.getInstance().acquire(true); + try { + return new Person(this.getPerson(), queryAccessControls(dbcon, personId), + queryEventAccessControls(dbcon, personId)); + } finally { + GolfDataSource.getInstance().release(dbcon); + } + } + + @Override + public Map get(Collection personIds) throws SQLException, IOException { + throw new UnsupportedOperationException(); + } + + private Set queryAccessControls(Connection dbcon, long personId) throws SQLException { + FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, SQL_SELECT_ACS); + try { + fps.setIntegerU(1, personId); + + Set set = new HashSet<>(); + fps.executeQuery().getFirstColumn(String.class, set); + return set; + } finally { + fps.close(); + } + } + + private Map> queryEventAccessControls(Connection dbcon, long personId) throws SQLException { + FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, SQL_SELECT_EVENT_ACS); + try { + fps.setIntegerU(1, personId); + return fps.executeQuery().getFirstTwoColumnsOneToMany(Long.class, String.class); + } finally { + fps.close(); + } + } + }; + + @Override + protected DataSet fetchOne(Connection dbcon, Long personId) throws SQLException { + String sql = SQL_SELECT_PERSON + "WHERE personID=?"; + FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, sql); try { fps.setIntegerU(1, personId); - return fps.executeQuery() - .getNextRow(); + return fps.executeQuery().getNextRow(); } finally { fps.close(); } } - private Set queryAccessControls(Connection dbcon, long personId) throws SQLException { - FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, SQL_SELECT_ACS); + @Override + protected Map fetchBulk(Connection dbcon, Collection personIds) throws SQLException { + String sql = SQL_SELECT_PERSON + "WHERE personID IN (??)"; + FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, sql, personIds); try { - fps.setIntegerU(1, personId); - - Set set = new HashSet<>(); - fps.executeQuery() - .getFirstColumn(String.class, set); - return set; - } finally { - fps.close(); - } - } - - private Map> queryEventAccessControls(Connection dbcon, long personId) throws SQLException { - FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, SQL_SELECT_EVENT_ACS); - try { - fps.setIntegerU(1, personId); - return fps.executeQuery() - .getFirstTwoColumnsOneToMany(Long.class, String.class); + return fps.executeQuery().getAllRows(Constants.PERSON_ID, Long.class); } finally { fps.close(); } @@ -84,6 +129,9 @@ public class PersonServiceDAO implements PersonService { + private static final String SQL_SELECT_PERSON = GolfSQL.changeSchema( + "SELECT P.* FROM ~g~.Person P "); + private static final String SQL_SELECT_ACS = GolfSQL.changeSchema( "SELECT AC.acSID " + "FROM ~g~.PersonAccessControl PAC " diff --git a/src/main/java/com/poststats/golf/service/db/SeriesServiceDAO.java b/src/main/java/com/poststats/golf/service/db/SeriesServiceDAO.java index 84c389d..0408172 100644 --- a/src/main/java/com/poststats/golf/service/db/SeriesServiceDAO.java +++ b/src/main/java/com/poststats/golf/service/db/SeriesServiceDAO.java @@ -2,81 +2,80 @@ 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.service.SeriesService; import com.poststats.golf.sql.GolfDataSource; import com.poststats.golf.sql.GolfSQL; import com.poststats.service.ServiceException; -import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.ApplicationScoped; import java.sql.Connection; import java.sql.SQLException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.util.Collection; +import java.util.Map; @ApplicationScoped -public class SeriesServiceDAO implements SeriesService { +public class SeriesServiceDAO extends CacheableServiceDAO implements SeriesService { - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - - @PostConstruct - public void init() { - this.logger.debug("SeriesServiceDAO init"); - } + private final long defaultCacheExpirationInSeconds = 3600; @Override - public DataSet get(int seriesId) { - Connection dbcon = GolfDataSource.getInstance() - .acquire(true); - try { - return this.querySeries(dbcon, seriesId); - } catch (SQLException se) { - throw new ServiceException(se); - } finally { - GolfDataSource.getInstance() - .release(dbcon); - } + public FlexMap get(int seriesId) { + return this.get(Integer.valueOf(seriesId)); } @Override public DataSet getByEventId(long eventId) { - Connection dbcon = GolfDataSource.getInstance() - .acquire(true); + Connection dbcon = GolfDataSource.getInstance().acquire(true); try { return this.querySeries(dbcon, eventId); } catch (SQLException se) { throw new ServiceException(se); } finally { - GolfDataSource.getInstance() - .release(dbcon); + GolfDataSource.getInstance().release(dbcon); } } - private static final String SQL_SELECT_SERIES = GolfSQL.changeSchema( - "SELECT * FROM ~g~.Series WHERE seriesID=? "); + @Override + protected long getCacheExpirationInSeconds() { + return this.defaultCacheExpirationInSeconds; + } - private DataSet querySeries(Connection dbcon, int seriesId) throws SQLException { - FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, SQL_SELECT_SERIES); + @Override + protected DataSet fetchOne(Connection dbcon, Integer seriesId) throws SQLException { + String sql = SQL_SELECT + "WHERE seriesID=?"; + FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, sql); try { fps.setSmallintU(1, seriesId); - return fps.executeQuery() - .getNextRow(); + return fps.executeQuery().getNextRow(); } finally { fps.close(); } } - private static final String SQL_SELECT_SERIES_BY_EVENT_ID = GolfSQL.changeSchema( - "SELECT * FROM ~g~.Series WHERE eventID=? "); + @Override + protected Map fetchBulk(Connection dbcon, Collection seriesIds) throws SQLException { + String sql = SQL_SELECT + "WHERE seriesID IN (??)"; + FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, sql, seriesIds); + try { + return fps.executeQuery().getAllRows("seriesID", Integer.class); + } finally { + fps.close(); + } + } private DataSet querySeries(Connection dbcon, long eventId) throws SQLException { FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, SQL_SELECT_SERIES_BY_EVENT_ID); try { fps.setIntegerU(1, eventId); - return fps.executeQuery() - .getNextRow(); + return fps.executeQuery().getNextRow(); } finally { fps.close(); } } + private static final String SQL_SELECT = GolfSQL.changeSchema( + "SELECT * FROM ~g~.Series "); + private static final String SQL_SELECT_SERIES_BY_EVENT_ID = GolfSQL.changeSchema( + "SELECT * FROM ~g~.Series WHERE eventID=? "); + } diff --git a/src/main/java/com/poststats/golf/servlet/EventFilter.java b/src/main/java/com/poststats/golf/servlet/EventFilter.java index f13e695..214b44b 100644 --- a/src/main/java/com/poststats/golf/servlet/EventFilter.java +++ b/src/main/java/com/poststats/golf/servlet/EventFilter.java @@ -24,21 +24,16 @@ public class EventFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext requestContext) throws IOException { - String eventIdStr = requestContext.getUriInfo() - .getPathParameters() - .getFirst(Constants.EVENT_ID); - eventIdStr = StringUtil.getInstance() - .trim(eventIdStr); + String eventIdStr = requestContext.getUriInfo().getPathParameters().getFirst(Constants.EVENT_ID); + eventIdStr = StringUtil.getInstance().trim(eventIdStr); if (eventIdStr == null) { - eventIdStr = requestContext.getUriInfo() - .getQueryParameters() - .getFirst(Constants.EVENT_ID); - eventIdStr = StringUtil.getInstance() - .trim(eventIdStr); + eventIdStr = requestContext.getUriInfo().getQueryParameters().getFirst(Constants.EVENT_ID); + eventIdStr = StringUtil.getInstance().trim(eventIdStr); } - if (eventIdStr == null) return; + if (eventIdStr == null) + return; this.logger.debug("Entering event context: {}", eventIdStr); @@ -46,7 +41,8 @@ public class EventFilter implements ContainerRequestFilter { requestContext.setProperty(Constants.EVENT_ID, eventId); SecurityContext scontext = requestContext.getSecurityContext(); - if (scontext.getUserPrincipal() == null) return; + if (scontext.getUserPrincipal() == null) + return; this.logger.debug("Narrowing authorization for event: {} => {}", scontext.getUserPrincipal(), eventId); @@ -54,7 +50,8 @@ public class EventFilter implements ContainerRequestFilter { if (this.logger.isTraceEnabled()) { Person person = (Person) epscontext.getUserPrincipal(); - this.logger.trace("Authorized event roles: {} => {}", person.getId(), person.getEventAccessControls(eventId)); + this.logger.trace("Authorized event roles: {} => {}", person.getId(), + person.getEventAccessControls(eventId)); } requestContext.setSecurityContext(epscontext); diff --git a/src/main/java/com/poststats/golf/servlet/PersonFilter.java b/src/main/java/com/poststats/golf/servlet/PersonFilter.java index e2e6367..2a0efa3 100644 --- a/src/main/java/com/poststats/golf/servlet/PersonFilter.java +++ b/src/main/java/com/poststats/golf/servlet/PersonFilter.java @@ -1,9 +1,5 @@ package com.poststats.golf.servlet; -import com.brianlong.cache.CacheException; -import com.brianlong.cache.CacheRetrievalException; -import com.brianlong.cache.MemoryCacher; -import com.brianlong.cache.Reaper; import com.poststats.golf.service.PersonService; import com.poststats.security.Person; import com.poststats.security.PersonSecurityContext; @@ -26,42 +22,26 @@ public class PersonFilter implements ContainerRequestFilter { private final Logger logger = LoggerFactory.getLogger(this.getClass()); - private MemoryCacher cacher = new MemoryCacher<>(60000L, new Reaper(30000L)); - @Inject private PersonService personService; @Override public void filter(ContainerRequestContext requestContext) throws IOException { SecurityContext scontext = requestContext.getSecurityContext(); - if (scontext.getUserPrincipal() == null) return; + if (scontext.getUserPrincipal() == null) + return; Person person = (Person) scontext.getUserPrincipal(); - try { - scontext = this.cacher.get(person.getId()); - } catch (CacheRetrievalException cre) { - this.logger.warn("This should never happen; if it does, skip cache", cre); - scontext = null; + + this.logger.debug("Gathering roles for golf: {}", person); + + com.poststats.golf.security.Person gperson = this.personService.getUserPrincipal(person); + + if (this.logger.isTraceEnabled()) { + this.logger.trace("Authorized roles: {} => {}", gperson.getId(), gperson.getAllAccessControls()); } - if (scontext != null) { - this.logger.debug("Using cached security context: {}", scontext.getUserPrincipal()); - } else { - this.logger.debug("Gathering roles for golf: {}", person); - - com.poststats.golf.security.Person gperson = this.personService.buildUserPrincipal(person); - - if (this.logger.isTraceEnabled()) { - this.logger.trace("Authorized roles: {} => {}", gperson.getId(), gperson.getAllAccessControls()); - } - - scontext = new PersonSecurityContext(gperson); - try { - this.cacher.add(person.getId(), scontext, false); - } catch (CacheException ce) { - this.logger.warn("This should never happen; if it does, caching failed", ce); - } - } + scontext = new PersonSecurityContext(gperson); requestContext.setSecurityContext(scontext); } diff --git a/src/main/java/com/poststats/golf/servlet/SeriesFilter.java b/src/main/java/com/poststats/golf/servlet/SeriesFilter.java index 5567682..93537f3 100644 --- a/src/main/java/com/poststats/golf/servlet/SeriesFilter.java +++ b/src/main/java/com/poststats/golf/servlet/SeriesFilter.java @@ -21,21 +21,16 @@ public class SeriesFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext requestContext) throws IOException { - String seriesIdStr = requestContext.getUriInfo() - .getPathParameters() - .getFirst(Constants.EVENT_SERIES_ID); - seriesIdStr = StringUtil.getInstance() - .trim(seriesIdStr); + String seriesIdStr = requestContext.getUriInfo().getPathParameters().getFirst(Constants.EVENT_SERIES_ID); + seriesIdStr = StringUtil.getInstance().trim(seriesIdStr); if (seriesIdStr == null) { - seriesIdStr = requestContext.getUriInfo() - .getQueryParameters() - .getFirst(Constants.EVENT_SERIES_ID); - seriesIdStr = StringUtil.getInstance() - .trim(seriesIdStr); + seriesIdStr = requestContext.getUriInfo().getQueryParameters().getFirst(Constants.EVENT_SERIES_ID); + seriesIdStr = StringUtil.getInstance().trim(seriesIdStr); } - if (seriesIdStr == null) return; + if (seriesIdStr == null) + return; this.logger.debug("Entering series context: {}", seriesIdStr); diff --git a/src/main/java/com/poststats/golf/transformer/EventTransformer.java b/src/main/java/com/poststats/golf/transformer/EventTransformer.java index 6b9beb0..cc8c072 100644 --- a/src/main/java/com/poststats/golf/transformer/EventTransformer.java +++ b/src/main/java/com/poststats/golf/transformer/EventTransformer.java @@ -1,6 +1,6 @@ package com.poststats.golf.transformer; -import com.brianlong.sql.DataSet; +import com.brianlong.util.FlexMap; import com.poststats.golf.api.model.Event; import com.poststats.transformer.Transformer; import jakarta.enterprise.context.ApplicationScoped; @@ -13,25 +13,22 @@ public class EventTransformer implements Transformer { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override - public Event toModel(DataSet row) { + public Event toModel(FlexMap row) { return this.toModel(row, new Event()); } @Override - public Event toModel(DataSet row, Event event) { - event.withId(row.getLong("eventID")) - .withName(row.getString("event")) - .withLiveline(row.getDate("liveline")) - .withDeadline(row.getDate("deadline")) - .withSeriesId(row.getInteger("seriesID")); + public Event toModel(FlexMap row, Event event) { + event.withId(row.getLong("eventID")).withName(row.getString("event")).withLiveline(row.getDate("liveline")) + .withDeadline(row.getDate("deadline")).withSeriesId(row.getInteger("seriesID")); StringBuilder location = new StringBuilder(); - if (row.isNotEmpty("addrcountry")) location.append(' ') - .append(row.getString("addrcountry")); - if (row.isNotEmpty("addrstate")) location.insert(0, ' ') - .insert(1, row.getString("addrstate")); - if (row.isNotEmpty("addrcity")) location.insert(0, ',') - .insert(0, row.getString("addrcity")); + if (row.isNotEmpty("addrcountry")) + location.append(' ').append(row.getString("addrcountry")); + if (row.isNotEmpty("addrstate")) + location.insert(0, ' ').insert(1, row.getString("addrstate")); + if (row.isNotEmpty("addrcity")) + location.insert(0, ',').insert(0, row.getString("addrcity")); this.logger.debug("Formulated event {} location: {}", row.getLong("eventID"), location); event.setLocation(location.toString()); diff --git a/src/main/java/com/poststats/golf/transformer/GolferTransformer.java b/src/main/java/com/poststats/golf/transformer/GolferTransformer.java index 1cfa1eb..b66d523 100644 --- a/src/main/java/com/poststats/golf/transformer/GolferTransformer.java +++ b/src/main/java/com/poststats/golf/transformer/GolferTransformer.java @@ -1,26 +1,21 @@ package com.poststats.golf.transformer; -import com.brianlong.sql.DataSet; +import com.brianlong.util.FlexMap; import com.poststats.api.model.Person; import com.poststats.transformer.Transformer; import jakarta.enterprise.context.ApplicationScoped; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @ApplicationScoped public class GolferTransformer implements Transformer { - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - @Override - public Person toModel(DataSet row) { + public Person toModel(FlexMap row) { return this.toModel(row, new Person()); } @Override - public Person toModel(DataSet row, Person person) { - person.withId(row.getLong("personID")) - .withFirstName(row.getString("fname")) + public Person toModel(FlexMap row, Person person) { + person.withId(row.getLong("personID")).withFirstName(row.getString("fname")) .withLastName(row.getString("lname")); return person; diff --git a/src/main/java/com/poststats/golf/transformer/SeriesTransformer.java b/src/main/java/com/poststats/golf/transformer/SeriesTransformer.java index 737f2cf..85fe360 100644 --- a/src/main/java/com/poststats/golf/transformer/SeriesTransformer.java +++ b/src/main/java/com/poststats/golf/transformer/SeriesTransformer.java @@ -1,26 +1,21 @@ package com.poststats.golf.transformer; -import com.brianlong.sql.DataSet; +import com.brianlong.util.FlexMap; import com.poststats.golf.api.model.Series; import com.poststats.transformer.Transformer; import jakarta.enterprise.context.ApplicationScoped; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @ApplicationScoped public class SeriesTransformer implements Transformer { - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - @Override - public Series toModel(DataSet row) { + public Series toModel(FlexMap row) { return this.toModel(row, new Series()); } @Override - public Series toModel(DataSet row, Series series) { - series.withId(row.getInteger("seriesID")) - .withName(row.getString("series")); + public Series toModel(FlexMap row, Series series) { + series.withId(row.getInteger("seriesID")).withName(row.getString("series")); return series; }