split to poststats-api/api-site

This commit is contained in:
2023-02-01 14:54:15 -05:00
parent cfdc7d2e36
commit 3e0e73ecb1
38 changed files with 1285 additions and 534 deletions

179
pom.xml
View File

@@ -1,54 +1,37 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.poststats</groupId>
<groupId>com.poststats.golf</groupId>
<artifactId>golf-api</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.release>11</maven.compiler.release>
<maven.compiler.debug>true</maven.compiler.debug>
<maven.compiler.debuglevel>lines,vars,source</maven.compiler.debuglevel>
<server.hostname>poststats.com</server.hostname>
<server.path.tomcat>/var/local/tomcat7-${env}</server.path.tomcat>
<server.path.configs>${server.path.tomcat}/shared/classes</server.path.configs>
<server.path.webapps>${server.path.tomcat}/webapps/golf.api.poststats.com</server.path.webapps>
</properties>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.bundles</groupId>
<artifactId>jaxrs-ri</artifactId>
<version>2.26</version>
<groupId>com.poststats</groupId>
<artifactId>poststats-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.poststats.golf</groupId>
<artifactId>golf-core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>com.poststats.golf</groupId>
<artifactId>db</artifactId>
<version>4.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.poststats</groupId>
<artifactId>db</artifactId>
<version>4.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.poststats</groupId>
<artifactId>commons-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
@@ -58,137 +41,19 @@
</dependencies>
<build>
<extensions>
<extension>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ssh</artifactId>
<version>2.10</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.4.8.v20171121</version>
<groupId>io.repaint.maven</groupId>
<artifactId>tiles-maven-plugin</artifactId>
<version>2.33</version>
<extensions>true</extensions>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
<connectors>
<connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
<port>8080</port>
<maxIdleTime>60000</maxIdleTime>
</connector>
</connectors>
<webApp>
<contextPath>/</contextPath>
<resourceBases>
<resourceBase>${project.build.directory}/${project.artifactId}-${project.version}</resourceBase>
</resourceBases>
<extraClasspath>${basedir}/../../poststats-db/db/src/${env},${basedir}/../../golf-db/db/src/${env},${basedir}/src/${env}/resources</extraClasspath>
</webApp>
<contextXml>${basedir}/src/${env}/jetty.xml</contextXml>
<useTestScope>true</useTestScope>
<tiles>
<tile>com.poststats:source-format-tile:[1.0.0,1.1.0)</tile>
</tiles>
</configuration>
</plugin>
<plugin>
<groupId>com.webcohesion.enunciate</groupId>
<artifactId>enunciate-maven-plugin</artifactId>
<version>2.9.1</version>
<configuration>
<docsDir>${project.build.directory}/${project.build.finalName}</docsDir>
<configFile>${basedir}/enunciate.xml</configFile>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>docs</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>digitalocean-env</id>
<activation>
<property>
<name>env</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>wagon-maven-plugin</artifactId>
<version>1.0</version>
<configuration>
<serverId>digitalocean-poststats</serverId>
</configuration>
<executions>
<execution>
<id>upload-config-to-server</id>
<phase>install</phase>
<goals>
<goal>upload</goal>
</goals>
<configuration>
<fromDir>${basedir}/src/${env}/resources</fromDir>
<url>scp://${server.hostname}${server.path.configs}</url>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>digitalocean-deploy</id>
<activation>
<property>
<name>env</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>wagon-maven-plugin</artifactId>
<version>1.0</version>
<configuration>
<serverId>digitalocean-poststats</serverId>
</configuration>
<executions>
<execution>
<id>upload-war-to-server</id>
<phase>install</phase>
<goals>
<goal>upload-single</goal>
</goals>
<configuration>
<fromFile>${project.build.directory}/${project.build.finalName}.war</fromFile>
<url>scp://${server.hostname}/tmp</url>
</configuration>
</execution>
<execution>
<id>deploy-war-in-server</id>
<phase>install</phase>
<goals>
<goal>sshexec</goal>
</goals>
<configuration>
<url>scp://${server.hostname}</url>
<commands>
<command>mv /tmp/${project.build.finalName}.war ${server.path.webapps}/ROOT.war</command>
</commands>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<Configure id="golf-api" class="org.eclipse.jetty.webapp.WebAppContext">
</Configure>

View File

@@ -1,8 +0,0 @@
package com.poststats.api.golf;
import javax.ws.rs.ApplicationPath;
@ApplicationPath("golf")
public class Application extends javax.ws.rs.core.Application {
}

View File

@@ -1,7 +0,0 @@
package com.poststats.api.golf.service;
public interface EventService {
int getSeriesId(long eventId);
}

View File

@@ -1,89 +0,0 @@
package com.poststats.api.golf.service.impl;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response.Status;
import com.brianlong.sql.DataSet;
import com.brianlong.sql.FlexPreparedStatement;
import com.poststats.api.golf.service.EventPersonService;
import com.poststats.sql.PostStatsDataSource;
public class DatabaseEventPersonService implements EventPersonService {
@Override
public List<DataSet> getPeople(long eventId) {
Connection dbcon = PostStatsDataSource.getInstance().acquire(true);
try {
return this.queryPersons(dbcon, eventId);
} catch (SQLException se) {
throw new WebApplicationException("Database call failure", se, Status.INTERNAL_SERVER_ERROR);
} finally {
PostStatsDataSource.getInstance().release(dbcon);
}
}
@Override
public List<DataSet> getParticipants(long eventId) {
Connection dbcon = PostStatsDataSource.getInstance().acquire(true);
try {
return this.queryParticipants(dbcon, eventId);
} catch (SQLException se) {
throw new WebApplicationException("Database call failure", se, Status.INTERNAL_SERVER_ERROR);
} finally {
PostStatsDataSource.getInstance().release(dbcon);
}
}
@Override
public Set<Long> getSeriesEventIdsAsParticipant(int seriesId, long personId) {
Connection dbcon = PostStatsDataSource.getInstance().acquire(true);
try {
return this.querySeriesEventIdsAsParticipant(dbcon, seriesId, personId);
} catch (SQLException se) {
throw new WebApplicationException("Database call failure", se, Status.INTERNAL_SERVER_ERROR);
} finally {
PostStatsDataSource.getInstance().release(dbcon);
}
}
private List<DataSet> queryPersons(Connection dbcon, long eventId) throws SQLException {
FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, SQL_SELECT_PERSONS);
try {
fps.setIntegerU(1, eventId);
return fps.executeQuery().getAllRows();
} finally {
fps.close();
}
}
private List<DataSet> queryParticipants(Connection dbcon, long eventId) throws SQLException {
FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, SQL_SELECT_PARTICIPANTS);
try {
fps.setIntegerU(1, eventId);
return fps.executeQuery().getAllRows();
} finally {
fps.close();
}
}
private Set<Long> querySeriesEventIdsAsParticipant(Connection dbcon, int seriesId, long personId) throws SQLException {
FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, SQL_SELECT_PARTICIPANTS);
try {
fps.setSmallintU(1, seriesId);
fps.setIntegerU(2, personId);
Set<Long> eventIds = new HashSet<>();
fps.executeQuery().getFirstColumn(Long.class, eventIds);
return eventIds;
} finally {
fps.close();
}
}
}

View File

@@ -1,38 +0,0 @@
package com.poststats.api.golf.service.impl;
import java.sql.Connection;
import java.sql.SQLException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response.Status;
import com.brianlong.sql.FlexPreparedStatement;
import com.poststats.api.golf.service.EventService;
import com.poststats.golf.sql.GolfSQL;
import com.poststats.sql.PostStatsDataSource;
public class DatabaseEventService implements EventService {
@Override
public int getSeriesId(long eventId) {
Connection dbcon = PostStatsDataSource.getInstance().acquire(true);
try {
return this.querySeriesId(dbcon, eventId);
} catch (SQLException se) {
throw new WebApplicationException("Database call failure", se, Status.INTERNAL_SERVER_ERROR);
} finally {
PostStatsDataSource.getInstance().release(dbcon);
}
}
private int querySeriesId(Connection dbcon, long eventId) throws SQLException {
FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, GolfSQL.changeSchema("SELECT seriesId FROM ~g~.Event WHERE eventID=?"));
try {
fps.setIntegerU(1, eventId);
return fps.executeQuery().getOne(Integer.class);
} finally {
fps.close();
}
}
}

View File

@@ -0,0 +1,76 @@
package com.poststats.golf.api;
import com.brianlong.sql.DataSet;
import com.fasterxml.jackson.core.JsonProcessingException;
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 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.MediaType;
import jakarta.ws.rs.core.Response.Status;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This API provides access to PostStats Golf Event meta-data and features.
*
* @author brian.long@poststats.com
*/
@RequestScoped
@Path("/event/{eventId}")
public class EventApi {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@PathParam("eventId")
private long eventId;
@Inject
private EventService eventService;
@Inject
private EventTransformer eventTransformer;
@PostConstruct
public void init() {
this.logger.debug("EventApi init: {}", this.eventId);
}
/**
* @return An event model object.
* @throws JsonProcessingException A JSON parsing issue occurred.
* @throws http-404 The specified event was not found.
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
public Event get() throws JsonProcessingException {
DataSet row = this.eventService.get(this.eventId);
if (row == null) throw new WebApplicationException("Event not found", Status.NOT_FOUND);
return this.eventTransformer.toModel(row);
}
/**
* @return An event model object.
* @throws JsonProcessingException A JSON parsing issue occurred.
* @throws http-404 The specified event was not found.
*/
@GET
@Path("/detail")
@Produces(MediaType.APPLICATION_JSON)
public EventDetail getDetail() throws JsonProcessingException {
DataSet row = this.eventService.getDetail(this.eventId);
if (row == null) throw new WebApplicationException("Event not found", Status.NOT_FOUND);
return (EventDetail) this.eventTransformer.toModel(row, new EventDetail());
}
}

View File

@@ -1,47 +1,48 @@
package com.poststats.api.golf;
package com.poststats.golf.api;
import com.brianlong.sql.DataSet;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.poststats.golf.service.EventFinanceService;
import jakarta.annotation.security.RolesAllowed;
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.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.SecurityContext;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.SecurityContext;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import com.brianlong.sql.DataSet;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.poststats.api.golf.service.EventFinanceService;
@RequestScoped
@Path("/event/{eventId}/finance")
public class EventFinanceApi {
private final ObjectMapper mapper = new ObjectMapper();
@PathParam("eventId")
private long eventId;
@Inject
private EventFinanceService eventFinanceService;
@GET
@Path("/balance/persons")
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed("member")
public String getBalanceByPersonsAsJson(@Context SecurityContext securityContext) throws JsonProcessingException {
if (!securityContext.isUserInRole(this.eventId + "~finance"))
throw new SecurityException("Not permitted");
List<DataSet> personsBalances = this.getEventFinanceService().getPersonsBalances(this.eventId);
if (!securityContext.isUserInRole(this.eventId + "~finance")) throw new SecurityException("Not permitted");
List<DataSet> personsBalances = this.eventFinanceService.getPersonsBalances(this.eventId);
List<Map<String, Object>> personsBalancesJson = new ArrayList<>(personsBalances.size());
for (DataSet personBalance : personsBalances) {
Map<String, Object> personBalanceJson = new HashMap<>(5);
@@ -51,53 +52,36 @@ public class EventFinanceApi {
personBalanceJson.put("balance", personBalance.getFloat("balance"));
personsBalancesJson.add(personBalanceJson);
}
return this.mapper.writeValueAsString(personsBalancesJson);
}
@GET
@Path("/balance/persons/csv")
@Produces("text/csv")
@RolesAllowed("member")
public String getBalanceByPersonsAsCsv(@Context SecurityContext securityContext) throws IOException {
if (!securityContext.isUserInRole(this.eventId + "~finance"))
throw new SecurityException("Not permitted");
List<DataSet> personsBalances = this.getEventFinanceService().getPersonsBalances(this.eventId);
if (!securityContext.isUserInRole(this.eventId + "~finance")) throw new SecurityException("Not permitted");
List<DataSet> personsBalances = this.eventFinanceService.getPersonsBalances(this.eventId);
StringBuilder personsBalancesCsvBuilder = new StringBuilder();
CSVPrinter personsBalancesCsvPrinter = new CSVPrinter(personsBalancesCsvBuilder, 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"),
personBalance.getFloat("paid"),
personsBalancesCsvPrinter.printRecord(personBalance.getLong("personID"), personBalance.getString("lname"), personBalance.getString("fname"),
personBalance.getString("suffix"), personBalance.getFloat("expense"), personBalance.getFloat("paid"),
personBalance.getFloat("balance"));
}
personsBalancesCsvPrinter.flush();
} finally {
personsBalancesCsvPrinter.close();
}
return personsBalancesCsvBuilder.toString();
}
private EventFinanceService getEventFinanceService() {
return ServiceLoader.load(EventFinanceService.class).iterator().next();
}
}

View File

@@ -1,5 +1,22 @@
package com.poststats.api.golf;
package com.poststats.golf.api;
import com.brianlong.sql.DataSet;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.poststats.golf.service.EventPersonService;
import com.poststats.golf.service.EventService;
import com.poststats.security.Person;
import jakarta.annotation.security.RolesAllowed;
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.QueryParam;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.SecurityContext;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
@@ -7,100 +24,86 @@ import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.SecurityContext;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import com.brianlong.sql.DataSet;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.poststats.api.golf.service.EventPersonService;
import com.poststats.api.golf.service.EventService;
import com.poststats.api.model.Person;
@RequestScoped
@Path("/event/{eventId}")
public class EventPersonApi {
private final ObjectMapper mapper = new ObjectMapper();
@PathParam("eventId")
private long eventId;
@Inject
private EventService eventService;
@Inject
private EventPersonService eventPersonService;
@GET
@Path("/people")
@Produces({MediaType.APPLICATION_JSON, "text/csv"})
@Produces({ MediaType.APPLICATION_JSON, "text/csv" })
@RolesAllowed("member")
public String get(@Context SecurityContext securityContext, @QueryParam("format") String format) throws JsonProcessingException, IOException {
if (!securityContext.isUserInRole(this.eventId + "~member"))
throw new SecurityException("Not permitted");
List<DataSet> persons = this.getEventPersonService().getPeople(this.eventId);
if ("csv".equals(format)) {
return this.toAddressBookAsCsv(persons);
} else {
return this.toAddressBookAsJson(persons);
}
}
@GET
@Path("/participants")
@Produces({MediaType.APPLICATION_JSON, "text/csv"})
@RolesAllowed("member")
public String getParticipants(@Context SecurityContext securityContext, @QueryParam("format") String format) throws JsonProcessingException, IOException {
if (!securityContext.isUserInRole(this.eventId + "~member"))
throw new SecurityException("Not permitted");
List<DataSet> persons = this.getEventPersonService().getParticipants(this.eventId);
if ("csv".equals(format)) {
return this.toAddressBookAsCsv(persons);
} else {
return this.toAddressBookAsJson(persons);
}
}
@GET
@Path("/participants")
@Produces({MediaType.APPLICATION_JSON, "text/csv"})
@RolesAllowed("member")
public String getSeriesParticipants(@Context SecurityContext securityContext, @QueryParam("format") String format) throws JsonProcessingException, IOException {
if (!securityContext.isUserInRole(this.eventId + "~member"))
throw new SecurityException("Not permitted");
int seriesId = this.getEventService().getSeriesId(this.eventId);
if (!securityContext.isUserInRole(this.eventId + "~member")) throw new SecurityException("Not permitted");
List<DataSet> persons = this.eventPersonService.getPeople(this.eventId);
if ("csv".equals(format)) {
return this.toAddressBookAsCsv(persons);
} else {
return this.toAddressBookAsJson(persons);
}
}
@GET
@Path("/participants")
@Produces({ MediaType.APPLICATION_JSON, "text/csv" })
@RolesAllowed("member")
public String getParticipants(@Context SecurityContext securityContext, @Context @QueryParam("format") String format)
throws JsonProcessingException, IOException {
if (!securityContext.isUserInRole(this.eventId + "~member")) throw new SecurityException("Not permitted");
List<DataSet> persons = this.eventPersonService.getParticipants(this.eventId);
if ("csv".equals(format)) {
return this.toAddressBookAsCsv(persons);
} else {
return this.toAddressBookAsJson(persons);
}
}
@GET
@Path("/participants")
@Produces({ MediaType.APPLICATION_JSON, "text/csv" })
@RolesAllowed("member")
public String getSeriesParticipants(@Context SecurityContext securityContext, @QueryParam("format") String format)
throws JsonProcessingException, IOException {
if (!securityContext.isUserInRole(this.eventId + "~member")) throw new SecurityException("Not permitted");
int seriesId = this.eventService.getSeriesId(this.eventId);
Person principal = (Person) securityContext.getUserPrincipal();
Set<Long> eventIds = this.eventPersonService.getSeriesEventIdsAsParticipant(seriesId, principal.getId());
Person principal = (Person)securityContext.getUserPrincipal();
Set<Long> eventIds = this.getEventPersonService().getSeriesEventIdsAsParticipant(seriesId, principal.getId());
Set<Long> personIds = new HashSet<>();
List<DataSet> persons = new LinkedList<>();
for (long eventId : eventIds) {
List<DataSet> tmpPersons = this.getEventPersonService().getParticipants(eventId);
List<DataSet> tmpPersons = this.eventPersonService.getParticipants(eventId);
for (DataSet person : tmpPersons)
if (personIds.add(person.getLong("personID")))
persons.add(person);
if (personIds.add(person.getLong("personID"))) persons.add(person);
}
if ("csv".equals(format)) {
return this.toAddressBookAsCsv(persons);
} else {
return this.toAddressBookAsJson(persons);
}
}
private String toAddressBookAsJson(List<DataSet> persons) throws JsonProcessingException {
List<Map<String, Object>> personsJson = new ArrayList<>(persons.size());
for (DataSet person : persons) {
@@ -113,46 +116,27 @@ public class EventPersonApi {
personJson.put("emailAddress", person.getString("email"));
personsJson.add(personJson);
}
return this.mapper.writeValueAsString(personsJson);
}
private String toAddressBookAsCsv(List<DataSet> persons) throws IOException {
StringBuilder personsCsvBuilder = new StringBuilder();
CSVPrinter personsCsvPrinter = new CSVPrinter(personsCsvBuilder, 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");
for (DataSet person : persons) {
personsCsvPrinter.printRecord(
person.getLong("personID"),
person.getString("prefix"),
person.getString("lname"),
person.getString("fname"),
person.getString("suffix"),
person.getString("email"));
personsCsvPrinter.printRecord(person.getLong("personID"), person.getString("prefix"), person.getString("lname"), person.getString("fname"),
person.getString("suffix"), person.getString("email"));
}
personsCsvPrinter.flush();
} finally {
personsCsvPrinter.close();
}
return personsCsvBuilder.toString();
}
private EventService getEventService() {
return ServiceLoader.load(EventService.class).iterator().next();
}
private EventPersonService getEventPersonService() {
return ServiceLoader.load(EventPersonService.class).iterator().next();
}
}

View File

@@ -0,0 +1,60 @@
package com.poststats.golf.api;
import com.brianlong.sql.DataSet;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.poststats.api.model.Person;
import com.poststats.golf.transformer.GolferTransformer;
import com.poststats.service.PersonService;
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.MediaType;
import jakarta.ws.rs.core.Response.Status;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This API provides access to PostStats Golfer meta-data and features.
*
* @author brian.long@poststats.com
*/
@RequestScoped
@Path("/golfer/{personId}")
public class GolferApi {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@PathParam("personId")
private long personId;
@Inject
private PersonService personService;
@Inject
private GolferTransformer golferTransformer;
@PostConstruct
public void init() {
this.logger.debug("GolferApi init: {}", this.personId);
}
/**
* @return An person model object.
* @throws JsonProcessingException A JSON parsing issue occurred.
* @throws http-404 The specified event was not found.
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
public Person get() throws JsonProcessingException {
DataSet row = this.personService.get(this.personId);
if (row == null) throw new WebApplicationException("Event not found", Status.NOT_FOUND);
return this.golferTransformer.toModel(row);
}
}

View File

@@ -0,0 +1,113 @@
package com.poststats.golf.api;
import com.brianlong.sql.DataSet;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.poststats.golf.api.model.Event;
import com.poststats.golf.api.model.Series;
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 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.QueryParam;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response.Status;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This API provides access to PostStats Golf Event Series meta-data and
* features.
*
* @author brian.long@poststats.com
*/
@RequestScoped
@Path("/series/{seriesId}")
public class SeriesApi {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@PathParam("seriesId")
private int seriesId;
@Inject
private SeriesService seriesService;
@Inject
private EventService eventService;
@Inject
private SeriesTransformer seriesTransformer;
@Inject
private EventTransformer eventTransformer;
@PostConstruct
public void init() {
this.logger.debug("SeriesApi init: {}", this.seriesId);
}
/**
* @return An event series model object.
* @throws JsonProcessingException A JSON parsing issue occurred.
* @throws http-404 The specified series was not found.
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
public Series get() throws JsonProcessingException {
DataSet row = this.seriesService.get(this.seriesId);
if (row == null) throw new WebApplicationException("Series not found", Status.NOT_FOUND);
return this.seriesTransformer.toModel(row);
}
/**
* @return An event series model object.
* @throws JsonProcessingException A JSON parsing issue occurred.
* @throws http-404 The specified series was not found or it had
* no associated events.
*/
@GET
@Path("/eventIds")
@Produces(MediaType.APPLICATION_JSON)
public Set<Long> getEventIds() throws JsonProcessingException {
Set<Long> eventIds = this.eventService.getIds(this.seriesId);
if (eventIds.isEmpty()) throw new WebApplicationException("Series or events not found", Status.NOT_FOUND);
return eventIds;
}
/**
* @param reverse `true` to sort events in reverse chronological order; `false`
* (default) otherwise.
* @return A list of event model objects.
* @throws JsonProcessingException A JSON parsing issue occurred.
* @throws http-404 The specified series was not found or it had
* no associated events.
*/
@GET
@Path("/events")
@Produces(MediaType.APPLICATION_JSON)
public List<Event> getEvents(@QueryParam("reverse") Boolean reverse) throws JsonProcessingException {
Map<Long, DataSet> rows = this.eventService.get(this.seriesId, !Boolean.TRUE.equals(reverse));
if (rows.isEmpty()) throw new WebApplicationException("Series or events not found", Status.NOT_FOUND);
List<Event> events = new LinkedList<Event>();
for (DataSet row : rows.values()) {
events.add(this.eventTransformer.toModel(row));
}
return events;
}
}

View File

@@ -1,26 +1,27 @@
package com.poststats.api.golf;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
package com.poststats.golf.api;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.TextNode;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@ApplicationScoped
@Path("/version")
public class VersionApi {
private ObjectMapper mapper = new ObjectMapper();
@GET
@Produces(MediaType.APPLICATION_JSON)
public String get() throws JsonProcessingException {
// FIXME make dynamic
return this.mapper.writeValueAsString(new TextNode("1.0"));
}
@GET
@Path("/pattern")
@Produces(MediaType.APPLICATION_JSON)

View File

@@ -0,0 +1,126 @@
package com.poststats.golf.api.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.annotation.Generated;
import java.time.LocalDate;
/**
* @author brian.long@poststats.com
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class Event {
@JsonProperty(required = true)
private long id;
@JsonProperty(required = true)
private String name;
@JsonProperty
private String location;
@JsonProperty
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "YYYY-MM-dd")
private LocalDate liveline;
@JsonProperty
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "YYYY-MM-dd")
private LocalDate deadline;
@JsonProperty
private int seriesId;
@Generated("Eclipse")
public long getId() {
return id;
}
@Generated("Eclipse")
public void setId(long id) {
this.id = id;
}
@Generated("Eclipse")
public String getName() {
return name;
}
@Generated("Eclipse")
public void setName(String name) {
this.name = name;
}
@Generated("Eclipse")
public String getLocation() {
return location;
}
@Generated("Eclipse")
public void setLocation(String location) {
this.location = location;
}
@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;
}
@Generated("Eclipse")
public int getSeriesId() {
return seriesId;
}
@Generated("Eclipse")
public void setSeriesId(int seriesId) {
this.seriesId = seriesId;
}
@Generated("Spark")
public Event withId(long id) {
this.id = id;
return this;
}
@Generated("Spark")
public Event withName(String name) {
this.name = name;
return this;
}
@Generated("Spark")
public Event withLocation(String location) {
this.location = location;
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;
}
@Generated("Spark")
public Event withSeriesId(int seriesId) {
this.seriesId = seriesId;
return this;
}
}

View File

@@ -0,0 +1,11 @@
package com.poststats.golf.api.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* @author brian.long@poststats.com
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class EventDetail extends Event {
}

View File

@@ -0,0 +1,50 @@
package com.poststats.golf.api.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.annotation.Generated;
/**
* @author brian.long@poststats.com
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class Series {
@JsonProperty(required = true)
private long id;
@JsonProperty(required = true)
private String name;
@Generated("Eclipse")
public long getId() {
return id;
}
@Generated("Eclipse")
public void setId(long id) {
this.id = id;
}
@Generated("Eclipse")
public String getName() {
return name;
}
@Generated("Eclipse")
public void setName(String name) {
this.name = name;
}
@Generated("Spark")
public Series withId(long id) {
this.id = id;
return this;
}
@Generated("Spark")
public Series withName(String name) {
this.name = name;
return this;
}
}

View File

@@ -0,0 +1,4 @@
/**
* Here is a description.
*/
package com.poststats.golf;

View File

@@ -0,0 +1,38 @@
package com.poststats.golf.security;
import com.poststats.security.Person;
import jakarta.ws.rs.core.SecurityContext;
import java.security.Principal;
public class EventPersonSecurityContext implements SecurityContext {
private final SecurityContext securityContext;
private final long eventId;
public EventPersonSecurityContext(SecurityContext securityContext, long eventId) {
this.securityContext = securityContext;
this.eventId = eventId;
}
@Override
public Principal getUserPrincipal() {
return this.securityContext.getUserPrincipal();
}
@Override
public boolean isUserInRole(String role) {
Person person = (Person) this.securityContext.getUserPrincipal();
return person.hasAccessControl(role, this.eventId);
}
@Override
public String getAuthenticationScheme() {
return this.securityContext.getAuthenticationScheme();
}
@Override
public boolean isSecure() {
return this.securityContext.isSecure();
}
}

View File

@@ -1,11 +1,10 @@
package com.poststats.api.golf.service;
import java.util.List;
package com.poststats.golf.service;
import com.brianlong.sql.DataSet;
import java.util.List;
public interface EventFinanceService {
List<DataSet> getPersonsBalances(long eventId);
}

View File

@@ -1,16 +1,15 @@
package com.poststats.api.golf.service;
package com.poststats.golf.service;
import com.brianlong.sql.DataSet;
import java.util.List;
import java.util.Set;
import com.brianlong.sql.DataSet;
public interface EventPersonService {
List<DataSet> getPeople(long eventId);
List<DataSet> getParticipants(long eventId);
Set<Long> getSeriesEventIdsAsParticipant(int seriesId, long personId);
}

View File

@@ -0,0 +1,19 @@
package com.poststats.golf.service;
import com.brianlong.sql.DataSet;
import java.util.Map;
import java.util.Set;
public interface EventService {
DataSet get(long eventId);
DataSet getDetail(long eventId);
Map<Long, DataSet> get(int seriesId, boolean chronological);
Set<Long> getIds(int seriesId);
int getSeriesId(long eventId);
}

View File

@@ -1,13 +1,12 @@
package com.poststats.api.golf.service;
import java.util.List;
package com.poststats.golf.service;
import com.brianlong.sql.DataSet;
import java.util.List;
public interface SeriesPersonService {
List<DataSet> getSeriesPeople(int seriesId);
List<DataSet> getSeriesParticipants(int seriesId);
}

View File

@@ -0,0 +1,11 @@
package com.poststats.golf.service;
import com.brianlong.sql.DataSet;
public interface SeriesService {
DataSet get(int seriesId);
DataSet getByEventId(long eventId);
}

View File

@@ -1,45 +1,54 @@
package com.poststats.api.golf.service.impl;
package com.poststats.golf.service.db;
import com.brianlong.sql.DataSet;
import com.brianlong.sql.FlexPreparedStatement;
import com.poststats.golf.service.EventFinanceService;
import com.poststats.golf.sql.GolfSQL;
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.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response.Status;
@ApplicationScoped
public class EventFinanceServiceDAO implements EventFinanceService {
import com.brianlong.sql.DataSet;
import com.brianlong.sql.FlexPreparedStatement;
import com.poststats.api.golf.service.EventFinanceService;
import com.poststats.golf.sql.GolfSQL;
import com.poststats.sql.PostStatsDataSource;
@PostConstruct
public void init() {
System.out.println("EventFinanceServiceDAO init");
}
public class DatabaseEventFinanceService implements EventFinanceService {
@Override
public List<DataSet> 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 WebApplicationException("Database call failure", se, Status.INTERNAL_SERVER_ERROR);
} finally {
PostStatsDataSource.getInstance().release(dbcon);
PostStatsDataSource.getInstance()
.release(dbcon);
}
}
private List<DataSet> queryPersonsBalances(Connection dbcon, long eventId) throws SQLException {
FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, SQL_SELECT_BALANCES);
try {
for (int i = 1; i <= 5; i++)
fps.setIntegerU(1, eventId);
return fps.executeQuery().getAllRows();
return fps.executeQuery()
.getAllRows();
} finally {
fps.close();
}
}
private static final String SQL_SELECT_BALANCES = GolfSQL.changeSchema(new StringBuilder()
.append("SELECT P.personID, P.prefix, P.fname, P.lname, P.suffix, ")
.append("TT.epersonID, SUM(TT.expenses) expenses, SUM(TT.paid) paid, (SUM(TT.paid)-SUM(TT.expenses)) balance ")

View File

@@ -0,0 +1,96 @@
package com.poststats.golf.service.db;
import com.brianlong.sql.DataSet;
import com.poststats.golf.service.EventPersonService;
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.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Set;
@ApplicationScoped
public class EventPersonServiceDAO implements EventPersonService {
@PostConstruct
public void init() {
System.out.println("EventFinanceServiceDAO init");
}
@Override
public List<DataSet> getPeople(long eventId) {
Connection dbcon = PostStatsDataSource.getInstance()
.acquire(true);
try {
return this.queryPersons(dbcon, eventId);
} catch (SQLException se) {
throw new WebApplicationException("Database call failure", se, Status.INTERNAL_SERVER_ERROR);
} finally {
PostStatsDataSource.getInstance()
.release(dbcon);
}
}
@Override
public List<DataSet> getParticipants(long eventId) {
Connection dbcon = PostStatsDataSource.getInstance()
.acquire(true);
try {
return this.queryParticipants(dbcon, eventId);
} catch (SQLException se) {
throw new WebApplicationException("Database call failure", se, Status.INTERNAL_SERVER_ERROR);
} finally {
PostStatsDataSource.getInstance()
.release(dbcon);
}
}
@Override
public Set<Long> getSeriesEventIdsAsParticipant(int seriesId, long personId) {
Connection dbcon = PostStatsDataSource.getInstance()
.acquire(true);
try {
return this.querySeriesEventIdsAsParticipant(dbcon, seriesId, personId);
} catch (SQLException se) {
throw new WebApplicationException("Database call failure", se, Status.INTERNAL_SERVER_ERROR);
} finally {
PostStatsDataSource.getInstance()
.release(dbcon);
}
}
private List<DataSet> 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<DataSet> 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 Set<Long> 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<Long> eventIds = new HashSet<>();
* fps.executeQuery().getFirstColumn(Long.class, eventIds); return eventIds; }
* finally { fps.close(); }
*/
}
}

View File

@@ -0,0 +1,170 @@
package com.poststats.golf.service.db;
import com.brianlong.sql.DataSet;
import com.brianlong.sql.FlexPreparedStatement;
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.HashSet;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ApplicationScoped
public class EventServiceDAO implements EventService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@PostConstruct
public void init() {
this.logger.debug("EventFinanceServiceDAO init");
}
@Override
public DataSet get(long eventId) {
Connection dbcon = GolfDataSource.getInstance()
.acquire(true);
try {
return this.queryEvent(dbcon, eventId, false);
} catch (SQLException se) {
throw new ServiceException(se);
} finally {
GolfDataSource.getInstance()
.release(dbcon);
}
}
@Override
public DataSet getDetail(long eventId) {
Connection dbcon = GolfDataSource.getInstance()
.acquire(true);
try {
return this.queryEvent(dbcon, eventId, true);
} catch (SQLException se) {
throw new ServiceException(se);
} finally {
GolfDataSource.getInstance()
.release(dbcon);
}
}
@Override
public Map<Long, DataSet> 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);
}
}
@Override
public Set<Long> 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);
try {
return this.querySeriesId(dbcon, eventId);
} catch (SQLException se) {
throw new ServiceException(se);
} finally {
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=? ");
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);
try {
fps.setIntegerU(1, eventId);
return fps.executeQuery()
.getNextRow();
} finally {
fps.close();
}
}
private static final String SQL_SELECT_EVENTS = GolfSQL.changeSchema(
"SELECT E.* "
+ "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<Long, DataSet> 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));
try {
fps.setSmallintU(1, seriesId);
return fps.executeQuery()
.getAllRows("eventID", Long.class);
} finally {
fps.close();
}
}
private static final String SQL_SELECT_EVENT_IDS = GolfSQL.changeSchema(
"SELECT eventID FROM ~g~.Event WHERE seriesID=? ");
private Set<Long> queryEventIds(Connection dbcon, int seriesId) throws SQLException {
Set<Long> 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();
}
}
}

View File

@@ -0,0 +1,82 @@
package com.poststats.golf.service.db;
import com.brianlong.sql.DataSet;
import com.brianlong.sql.FlexPreparedStatement;
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;
@ApplicationScoped
public class SeriesServiceDAO implements SeriesService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@PostConstruct
public void init() {
this.logger.debug("SeriesServiceDAO init");
}
@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);
}
}
@Override
public DataSet getByEventId(long eventId) {
Connection dbcon = GolfDataSource.getInstance()
.acquire(true);
try {
return this.querySeries(dbcon, eventId);
} catch (SQLException se) {
throw new ServiceException(se);
} finally {
GolfDataSource.getInstance()
.release(dbcon);
}
}
private static final String SQL_SELECT_SERIES = GolfSQL.changeSchema(
"SELECT * FROM ~g~.Series WHERE seriesID=? ");
private DataSet querySeries(Connection dbcon, int seriesId) throws SQLException {
FlexPreparedStatement fps = new FlexPreparedStatement(dbcon, SQL_SELECT_SERIES);
try {
fps.setSmallintU(1, seriesId);
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=? ");
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();
} finally {
fps.close();
}
}
}

View File

@@ -0,0 +1,57 @@
package com.poststats.golf.servlet;
import com.brianlong.util.StringUtil;
import com.poststats.golf.Constants;
import com.poststats.golf.security.EventPersonSecurityContext;
import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.PreMatching;
import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.ext.Provider;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ApplicationScoped
@Provider
@PreMatching
@Priority(Priorities.AUTHORIZATION + 10)
public class EventFilter implements ContainerRequestFilter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
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);
}
if (eventIdStr == null) return;
this.logger.debug("Entering event context: {}", eventIdStr);
long eventId = Long.valueOf(eventIdStr);
requestContext.setProperty(Constants.EVENT_ID, eventId);
SecurityContext scontext = requestContext.getSecurityContext();
if (scontext != null) {
this.logger.debug("Authorized for Event: {} => {}", scontext.getUserPrincipal()
.getName(), eventId);
requestContext.setSecurityContext(new EventPersonSecurityContext(scontext, eventId));
}
}
}

View File

@@ -0,0 +1,48 @@
package com.poststats.golf.servlet;
import com.brianlong.util.StringUtil;
import com.poststats.golf.Constants;
import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.PreMatching;
import jakarta.ws.rs.ext.Provider;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ApplicationScoped
@Provider
@PreMatching
@Priority(Priorities.AUTHORIZATION + 5)
public class SeriesFilter implements ContainerRequestFilter {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
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);
}
if (seriesIdStr == null) return;
this.logger.debug("Entering series context: {}", seriesIdStr);
int seriesId = Integer.valueOf(seriesIdStr);
requestContext.setProperty(Constants.EVENT_SERIES_ID, seriesId);
}
}

View File

@@ -0,0 +1,41 @@
package com.poststats.golf.transformer;
import com.brianlong.sql.DataSet;
import com.poststats.golf.api.model.Event;
import com.poststats.transformer.Transformer;
import jakarta.enterprise.context.ApplicationScoped;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ApplicationScoped
public class EventTransformer implements Transformer<Event> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public Event toModel(DataSet 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"));
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"));
this.logger.debug("Formulated event {} location: {}", row.getLong("eventID"), location);
event.setLocation(location.toString());
return event;
}
}

View File

@@ -0,0 +1,29 @@
package com.poststats.golf.transformer;
import com.brianlong.sql.DataSet;
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<Person> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public Person toModel(DataSet row) {
return this.toModel(row, new Person());
}
@Override
public Person toModel(DataSet row, Person person) {
person.withId(row.getLong("personID"))
.withFirstName(row.getString("fname"))
.withLastName(row.getString("lname"));
return person;
}
}

View File

@@ -0,0 +1,28 @@
package com.poststats.golf.transformer;
import com.brianlong.sql.DataSet;
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<Series> {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public Series toModel(DataSet row) {
return this.toModel(row, new Series());
}
@Override
public Series toModel(DataSet row, Series series) {
series.withId(row.getInteger("seriesID"))
.withName(row.getString("series"));
return series;
}
}

View File

@@ -0,0 +1,5 @@
<beans version="4.0"
xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd">
</beans>

View File

@@ -1 +0,0 @@
com.poststats.api.golf.service.impl.DatabaseEventFinanceService

View File

@@ -1 +0,0 @@
com.poststats.api.golf.service.impl.DatabaseEventPersonService

View File

@@ -1 +0,0 @@
com.poststats.api.golf.service.impl.DatabaseEventService

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>PostStats Golf API</display-name>
<context-param>
<param-name>envConfig</param-name>
<param-value>/env-config.golf-api.properties</param-value>
</context-param>
<!--
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>default</realm-name>
</login-config>
-->
<session-config>
<session-timeout>300</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>

View File

@@ -1,44 +1,45 @@
package com.poststats.api.golf;
package com.poststats.golf.api;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.regex.Pattern;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import com.poststats.api.golf.VersionApi;
public class VersionApiUnitTest {
private static ObjectMapper om = new ObjectMapper();
private static VersionApi api;
@BeforeClass
public static void init() {
api = new VersionApi();
}
@Test
public void get() throws IOException {
String current = api.get();
Assert.assertNotNull(current);
Assert.assertTrue(current.length() > 0);
}
@Test
public void getPattern() throws IOException {
String pattern = api.getRegexPattern();
Assert.assertNotNull(pattern);
Assert.assertTrue(pattern.length() > 0);
Pattern.compile(pattern);
Pattern.compile(om.readValue(pattern, String.class));
}
@Test
public void isValidFormat() throws IOException {
String pattern = api.getRegexPattern();
String current = api.get();
Assert.assertTrue(Pattern.compile(pattern).matcher(current).matches());
String pattern = om.readValue(api.getRegexPattern(), String.class);
String current = om.readValue(api.get(), String.class);
Assert.assertTrue(Pattern.compile(pattern)
.matcher(current)
.matches());
}
}

View File

@@ -0,0 +1,19 @@
rootLogger.level = debug
rootLogger.appenderRef.stdout.ref = api
appender.rolling.type = RollingFile
appender.rolling.name = api
appender.rolling.fileName = ${catalina.base}/logs/api.log
appender.rolling.filePattern = ${catalina.base}/logs/api-%d{MM-dd-yyyy}.log.gz
appender.rolling.layout.type = PatternLayout
appender.rolling.layout.pattern = %d %p %C{1.} [%t] %m%n
appender.rolling.policies.type = Policies
appender.rolling.policies.time.type = TimeBasedTriggeringPolicy
appender.rolling.strategy.type = DefaultRolloverStrategy
appender.rolling.strategy.max = 10
logger.brianlong.name = com.brianlong
logger.brianlong.level = debug
logger.poststats.name = com.poststats
logger.poststats.level = debug