WTI API Project Implementation Details - pc2ccs/pc2v9 GitHub Wiki

Overview

WTI-API project is an Eclipse Java EE (Enterprise Edition) project which provides the "back-end" code for the PC2v9 Web Team Interface (WTI). The WTI-API project provides a webserver which exposes an API which contest teams can invoke from a browser and use to access a running PC² contest system. For this reason, the WTI-API project is also referred to as the PC2v9 web team server.

The services provided by the WTI-API project are typically accessed by the WTI-UI project, which implements the "front-end" (browser-side) portion of the overall WTI web team interface. Like the WTI-UI project, the WTI-API Java Project is housed in the projects folder of the pc2v9 project.

The WTI project (both the front-end -UI component and the back-end -API component) was developed by a team of students from Eastern Washington University (EWU) consisting of Josie Isaacson, Andrew Combs, Ethan Holman, and Danielle Frodge, under the direction of EWU Professor Tom Capaul. The project was ported into the Eclipse and pc2v9 GitHub environments by John Clevenger.

The WTI API REST endpoints are documented in the article WTI-API

The WTI-API project comprises several components, including:

  • a Swagger/OpenAPI API specification which defines the REST API accessible by the WTI-UI front-end;
  • a Jetty webserver which serves web pages (via the Swagger API) to the front-end WTI-UI project;
  • Jersey, an implementation of the JAX-RS specification, for handling HTTP requests made to the Jetty webserver;
  • Jackson, a set of tools for parsing and processing JSON;
  • a set of back-end services which listen via the PC2v9 API to a running PC² Server and forward messages via websockets from the PC² Server to the WTI-UI front-end code running in team browsers;
  • a websockets package which provides support for passing messages between the WTI-API back-end and the WTI-UI front-end.

Project Environment

WTI-API is implemented as a Java EE dynamic web application project. Eclipse with support for Java EE (Enterprise Edition) Version 8 or higher is recommended when developing code for the WTI-API project.

The following tool versions are contained in the WTI-API project libraries:

  • Jetty - version 9.4.14 - 9.4.15
  • Swagger - version 1.5.21
  • Jackson - version 2.9.8
  • Jersey - version 2.28

Swagger/OAS API

The WTI-API project creates an Application Program Interface (API) which is accessed by the WTI-UI front-end project. The API is documented using Swagger. Specifically, starting the WTI-API web team server (using the default settings) and then opening http://localhost:8080/swagger will return a Swagger web page showing the definition of the WTI-API project API endpoints. This documents the API which the WTI-UI front-end (or any client of WTI-API) can access.

The Swagger API documentation is dynamically generated at runtime by the Swagger libraries which are attached to the project's Jetty server (see below). Conceptually, the API is described by a file swagger.json (meaning, this is the file which specifies the content of the API web page displayed by opening the http://localhost:8080/swagger URL). The actual swagger.json text can be viewed by opening http://localhost:8080/api/swagger.json.

Note that there is not actually any swagger.json file anywhere in the project. Rather, when the Jetty server Swagger endpoints are referenced, the associated Swagger library routines scan the web app code for JAX-RS and Swagger annotations and use the resulting information to dynamically construct the Swagger API documentation. An important characteristic of this approach is that the Swagger documentation will always be "up to date"; any changes to API endpoint code in the project will be automatically reflected in the dynamically-generated documentation.

WTI-API Architecture Overview

The basic architecture of the WTI-API component of the PC² Web Team Interface is described by the following diagram; the following sections provide an explanation of the various elements shown in the diagram.

WTI-API Architecture

Starting the WTI-API (see below) creates an instance of a Jetty server, configured to respond to WTI-API HTTP requests. The startup sequence also arranges that when an external client (such as the WTI-UI project) references the /api/teams/login endpoint in the server, the server transfers control to the login() method of an instance of the TeamsController class.

The TeamsController.login() method constructs a new pc2.api.ServerConnection instance for the new team logging in, and also constructs a new UUID to uniquely identify the team. It then invokes the ServerConnection's login() method, which creates within the ServerConnection a Contest object (an instance of pc2.api.implementation.Contest) and a Controller object (an instance of pc2.core.InternalController). These two objects act as the ServerConnection's model and controller for the contest to which the team has connected (thus, each team ServerConnection has its own contest model and controller).

ServerConnection.login() then invokes the internal controller's login() method, which uses the Transport Layer to connect to and log in to the PC² server. Once the ServerConnection has logged in to the PC² server, the TeamsController.login() method invokes the subscribe() method in the MainController parent class, which registers listeners for run judgements, clarification responses, and contest configuration updates with the contest in the ServerConnection. Any changes in the state of the contest being managed by the PC² server cause updates in the contest within each team's ServerConnection, which in turn results in callbacks to the registered listeners for each WTI connected team.

Constructing a TeamsController also includes creation of a websocket to be used for communication between the team clients and the WTI server. Each listener is a new instance of a WTI-API service which is given the unique key (id) for the team, along with the websocket for the team, when it is constructed.

Finally, as the last step in the login() sequence, the TeamsController saves the team's ServerConnection in a map (contained in the parent MainController class) under the team's unique key (thus, the MainController contains a collection of all the ServerConnection objects, one per logged-in team).

Once login() is complete the TeamsController returns a response to the browser; the response includes the unique team key, which subsequent browser API references are expected to provide when making connections regarding this team's session.

The startup sequence (again, see below) also arranges that when a team subsequently makes a POST to an API endpoint such as /api/teams/run (to submit a run) or to /api/teams/clarification (to submit a clarification request), such references transfer to the appropriate TeamsController method (e.g. submitRun() or submitClarification()). These methods use the provided teamId key to look up the team's connection to the PC² server and then invoke the corresponding PC² server method via the PC2v9 API.

When one of the WTI-API service classes registered as a listener with the contest in PC2API ServerConnection receives a callback , it constructs a message identifying the type of callback (e.g. "run XX has been judged" or "clarification YY has been answered") and sends that message to the team client via the corresponding websocket.

Note that how the client deals with the message is up to the client code, but typically (for example in the case of the WTI-UI client code) it examines the message data then makes a call to the appropriate WTI-API endpoint to obtain additional information related to the message. For example, a "run XX has been judged" message sent to the client's websocket will cause the WTI-UI client code to request runs from the WTI-API /api/teams/run endpoint, search through the returned runs for the specified run XX, and display the updated status of run XX to the team. (See WTI-UI Websocket Messages for further details.)

The WTI-API startup sequence also causes creation of an instance of ContestController, which contains endpoints invoked via Jetty to provide general contest services. For example, if a team browser references the api/contest/languages endpoint, the ContestController uses the teamId to obtain the team's ServerConnection, then invokes the getLanguages() API method in the contest object within that ServerConnection to obtain and return a list of the contest languages.

The ContestController also supports the ability of the WTI-API to provide contest standings; this is described separately at the WTI-API Scoreboard Implementation page.

Top-level Project Organization

The WTI-API project is organized into three primary "source code" packages:

  • src, which contains a folder main housing source for the major WTI-API components, organized into the following subfolders:
    • config, holding the classes used for configuring and managing the Jetty server, as well as for providing the project logging facilities
    • controllers, containing an abstract MainController class and its two concrete subclasses TeamsController and ContestController.
    • emptyObjs, comprised of empty "template" classes for IProblem and ILanguage implementations.
    • jerseyConfig, which holds classes to handle exceptions which occur in Jersey when using Jackson to perform JSON mapping.
    • models, containing descriptions of the model objects (the data types) which make up the model components manipulated by the controllers in the MVC architecture.
    • services, which holds callback service routines which are invoked by the PC² server (via the PC2v9 API and which send messages via websockets to the team clients.
  • websockets, which houses types and classes used to support websocket communications between the WTI-UI front-end and this WTI-API back-end
  • tests, a collection of JUnit tests for various WTI-API components.

The top-level project also contains the following:

  • a set of libraries holding library packages used by both the WTI-API front-end and the WTI-UI back-end projects.
  • build and dist folders used during the WTI-API build process for creating distributable versions of the project. (Note that build and/or dist may be empty, depending on whether and how the WTI-API project has been built on the current system. See WTI Build Package Details for additional information).
  • demo, a folder holding the final "demo" version of the WTI project by the EWU student team (which may still be relevant because it may include efforts at demoing the not-completed support for Test Runs in the WTI system).
  • doc, a folder containing both the current WTI project documentation and the set of documentation developed by the EWU students during their project. The doc/User Manual folder contains current user-level documentation. Note however that the content in the doc/Tech Docs folder is (mostly) documentation from the original EWU student project and is out of date with respect to the current state of the project.
  • scripts, holding the Windows and Unix scripts used to start the WTI web team server.
  • testfiles, which contains a collection of "HelloWorld" programs in several languages used for testing the WTI system.
  • WebContent, a folder holding the "dynamic web content" for the WTI project. This includes the following sub-folders:
    • WEB_INF, containing a lib folder holding the .jar libraries used by the web application.
    • webapp, containing the resources (such as the Swagger definition files) defining the external view of the web application.

In addition, the top level of the WTI-API project contains standard Eclipse/Git files such as .classpath, .project, and .gitignore. It also contains the buildWTI.xml and packageWTI.xml files used for building the project, along with the pc2v9.ini file used to initialize the project at runtime. The readme.md file that accompanied the original EWU project is also included, although it is likely out of date.

Code Details

Startup Sequence

The normal method of starting the WTI-API is to use the command pc2wti, which invokes the main entry point for the WTI-API web application: config.JettyConfig.main(String[] args). This entry point (herein after referred to as just main()) is the entry point which is configured into the WebTeamInterface-1.1.jar manifest (and hence is what gets executed when WebTeamInterface-1.1.jar is invoked as a runnable jar, which is what pc2wti does).

The main() method creates a ServerInit object, which instantiates a Logging object for handling logging and then reads the pc2v9.ini file to obtain configuration parameters (such as the port number on which the Jetty server should listen for browser connections, the base name to be used for naming websockets, and the scoreboard account and password which the WTI server will use to obtain scoreboard information from the PC² server). These logging and configuration parameter values are saved in the ServerInit object.

Next, main() invokes ServerInit static method updateUIAppConfig(). This method obtains the IP address for the machine on which it is running and then constructs URLs for HTTP and WebSocket (ws) connections to the machine using the local IP address along with the port and websocket name values read from the pc2v9.ini file. It then saves these URLs in a file WebContent/WTI-UI/assets/appconfig.json where is is subsequently used by the WTI-UI project (see WTI-UI-Project-Implementation-Details#environments).

Finally, main() invokes WebServer.startServer(), passing it the ServerInit object created above. This method creates a list of Jetty handlers, including

  • a ServletContextHandler websocket handler which is initialized with WTI class communication.WTIWebsocketMediator as the class to which websocket requests at ServerEndpoint("/api/WTISocket/{team_id}") should be routed.
  • a ContextHandler swagger handler which is initialized with the Web resource information found in ./WebContent/webapp, which includes the WTI-API project Swagger interface definition JavaScript files.
  • a ContextHandler webapp handler which is initialized with the Web resource information found in ./WebContent/WTI-UI, which contains the assets and resources of the WTI-UI front-end project. (Note that there is no ./WebContent/WTI-UI folder in the WTI-API Java project; this folder structure is put into the runtime distribution by the WTI-API build process -- see WTI Build Package Details for additional information.)
  • a ServletContextHandler jersey api handler which is initialized with a Context Path of /api and with a set of Servlets including:
    • a ServletContainer holding a variety of service provider class names (places where Jersey should look for resources implementing path endpoints). The service providers in the ServletContainer include the following classes which map to the path /*:
      • org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJaxbJsonProvider
      • org.glassfish.jersey.jackson.JacksonFeature
      • the WTI TeamsController
      • the WTI ContestController
    • a Servlet containing the Swagger class DefaultJaxrsConfig which maps to the path /swagger-core
    • a jetty.servlet.FilterHolder containing filtering support for CORS.

The net effect of the above is to define a list of handlers for the various WTI end-points.

startServer() then instantiates a Jetty Server listening on the specified port, sets the handler list into the Server, and starts the Server running. The overall effect is that the Jetty server listens at the specified port for requests having the /api context path and routes all such requests to the appropriate Jersey, Swagger, or websocket servlet.

The final step in startServer() is to execute server.join(), which has the effect of blocking until the Jetty server is terminated.

Controllers

Controller Organization

Controllers are classes which implement the "controller" function in the MVC architecture. Conceptually the top-level controller is the class MainController. However, MainController is abstract; it has two concrete subclasses TeamsController and ContestController.

TeamsController acts as the MVC controller for functions related to individual teams, such as login/logout, run submission, and clarification submission. ContestController is the MVC controller for operations related to fetching information about the contest itself, such as languages, problems, judgement types, and contest state. MainController contains the features shared between the two types of concrete controllers -- for example, a HashMap mapping team ids to their PC² server connection sessions. "Team ids" are UUIDs which are unique for each team. Because this HashMap is static it is shared between all instances of the (concrete subclasses of) MainController.

MainController also contains two other static fields: client and logger. Both of these are initialized by MainController's constructor (meaning, whenever a concrete subclass is instantiated). loggeris initialized with a new config.Logging object (which uses a java.util.logging.Logger object to handle logging). client is initialized with with a new WTIWebsocket, which is the server-side websocket connection point accessed by clients when they construct websocket connections to the WTI-API web team server.

The TeamsController class is annotated with @Path("/teams"), which means that HTTP references to paths beginning with /api/teams are routed to a TeamsController object. Similarly, the @Path("/contest") annotation on the ContestController class causes references to paths beginning with /api/contest to be routed to a ContestController object.

The TeamsController and ContestController classes also have @Singleton annotations. This is a Jersey annotation which instructions Jersey to treat resource classes as singletons -- meaning, not to instantiate a new object for every reference to the corresponding endpoint but rather to instantiate the class just once and to reuse that instance for subsequent endpoint references.

Certain methods within TeamsController and ContestController are also annotated with @GET or @POST annotations and with @Path annotations (for example method TeamsController.login() has the annotations @POST and @Path("/login"); method ContestController.languages() has the annotations @GET and @Path("/languages")). These annotations make these methods what are sometimes called "sub-resources", and they act as expected: an HTTP reference to a path with the corresponding value transfers to the corresponding method (for example, an HTTP POST specifying path /api/teams/login transfers to the TeamsController.login() method; an HTTP GET specifying path /api/contest/languages transfers to the ContestController.languages() method).

Certain controller methods also have additional annotations. For example, the TeamsController.login() method is annotated thusly:

	@ApiOperation(value = "User login",
	notes = "Creates a new session to WTI API. Once successfully created, users have access to all of WTI featues available within system.",
	response = LoginRequestModel.class,  //should this be LoginResponseModel ??
	responseContainer = "List")
	@ApiResponses({
		@ApiResponse(code = 200, message = "Returned if the login is valid", response = LoginResponseModel.class), 
		@ApiResponse(code = 401, message = "Returned if invalid credentials are supplied", response = ServerErrorResponseModel.class)
	})

Annotations of this type are Swagger annotations. They allow the Swagger libraries embedded within the WIT-API project to detect the methods that constitute the Swagger API defined by the project.

Controller Details

The process of instantiating a TeamsController includes executing the abstract superclass (MainController) constructor, which creates a new WTIWebsocket connection between the team client and the WTI server. This websocket is subsequently stored in each of the service objects (found in the services package) which is created during execution of the login() method in the TeamsController.

TeamsController registers listeners with the PC² server using the following method (found in the MainController parent class). Parameter teamCon is the IContest object representing the PC2 contest, while teamId is the unique UUID associated by the login() method with each logged-in team. client is the websocket connection which each service used to send messages to the team client.

	protected void subscription(Contest teamCon, String teamId) {
		teamCon.addRunListener(new RunsService(teamId, client));
		teamCon.addTestRunListener(new TestRunService(teamId, client));
		teamCon.addClarificationListener(new ClarificationService(teamId, client));
		teamCon.addContestConfigurationUpdateListener(new ConfigurationService(teamId, client));
	}

The effect of this is to connect a listener for each kind of service to the PC² server; when the PC² server calls back to the service listener that service uses the "client" websocket to forward a corresponding message to the team client.

Services

The services package primarily contains classes for handling callbacks received from the PC² server. Each of these service classes (ClarificationService, ConfigurationService, RunsService, and TestRunService) essentially does the same thing: construct a JSON message containing information received from the PC² server and send that message to the client via the client websocket.

The services package also contains a utility class FileService which is used by the TeamsController to map files between the WTI-API data model File type and the file representations used in PC².

Websocket Message Handling

Websocket connections between the WTI-UI front-end and the WTI-API back-end are initiated during the login process invoked by the WTI-UI front-end (see WTI-UI websockets).

Incoming websocket requests received by Jetty are routed to the WTIWebsocketMediator class. This class defines four methods, each with a Java EE Annotation indicating the conditions under which it is invoked:

  • @OnOpen connect(): this method receives the javax.websocket.Session and the team-id associated with the newly-opened websocket and saves them in a global (static) table (actually, a Set) named sessions.
  • @OnMessage message(): this method receives a Session, a String message, and a teamId; it formats the message as a JSON object, looks in the sessions table for a websocket session associated with the specified team, and sends the message to the team's websocket.
  • @OnClose close(): this method receives a Session and a teamId; it searches the current sessions table for a Session associated with the specified teamId and if such a session is found it is removed from the table.
  • @OnWebsocketError onError(): this method is invoked when a websocket error has occurred. It currently just displays an error message on the console; this should be changed so that errors are logged.

JUnits

The WTI-API project contains a number of JUnits, found in the WTI-API/test folder. The JUnit tests fall into two categories, each contained in the corresponding folder under test:

  • controllers -- tests for checking the ContestController and TeamsController classes.
  • websockets -- tests for verifying proper operation of websocket communication between the WTI server and clients.

In addition, the test folder contains the following:

  • utils, a set of controller injection classes which are used by the controllers JUnit tests (see below).
  • suites, which defines a suite of tests to be run using the JUnit4 Suite capability. (It's not clear why the defined suite contains only tests that are also run as part of the controllers tests; you'd have to ask the original EWU student project implementers...)

Controller Injection in JUnit Tests

One of the difficulties with running JUnit tests for the WTI-API project is that nearly all WTI-API functions require access to a running PC² server. This means that in order to run JUnit tests it would normally be necessary to first start up a PC² server with a fully-configured contest.

To avoid this, the WTI-API JUnit tests create a mock PC² server connection, using Mockito. The setup() method in each ContestController JUnit test constructs a ContestControllerInjection object which contains a Mockito mock PC² server connection and includes a set of Mockito mock contest classes (for example, an ILanguage object, an IProblems object, and all of the other classes which a ServerConnection would need to access in a real PC² contest).

This ContestControllerInjection object is then "injected" (using Java Reflection) by the setup() method into the ContestController which will be invoked by the JUnit test methods which run following setup(). (More accurately, it is injected into the MainController class which is the parent class of ContestController.) Each JUnit test which is testing some function of the ContestController is then responsible for configuring the "mock contest" classes with the data which the test would like to be returned from a real PC² server (if such a server was running).

Once a JUnit test has properly configured the (mock) contest, then when the test invokes a ContestController method the controller will access the mock PC² server connection and use it to fetch mock contest data, which it returns to the JUnit test method. In this way JUnits can perform full testing of the controllers without the need for having an actual PC² server running.

The JUnits which test the TeamsController methods operate the same as above: their setup() method injects a mock server connection (with a mock contest) into the TeamsController class, and each JUnit test configures the (mock) contest as required prior to invoking TeamsController methods.

Note that one awkward feature of the implementation of the above is that in order to give the JUnit tests access to the appropriate controller injection class, the JUnits are defined as subclasses of the corresponding controller injection class. Together with the fact that the mock contest fields defined in the injection classes have protected access, this allows the JUnits to directly access the corresponding mock contest fields. This is a bit of a misuse of inheritance and should probably be refactored at some future point...

See Also