Multiclass APIs

If a single API is particularly complex, you might want to implement it from multiple Java classes. To make different classes part of the same API, you must:

For example, the following two classes are both part of the tictactoe API:

@Api(name = "tictactoe", version = "v1") class TicTacToeA {  } @Api(name = "tictactoe", version = "v1") class TicTacToeB {  } 

The API configuration is specified through the @Api annotation properties. However, for multiple classes in the same API, the @Api requirements extend beyond simply having the same name and version strings in the @Api annotation for each class. In fact, your backend API will not work if there are any differences in the API configurations specified in the classes' @Api properties. Any difference in the @Api properties for classes in a multiclass API result in an "ambiguous" API configuration, which will not work in Cloud Endpoints Frameworks for App Engine.

There are several ways to create a unambiguous multiclass API:

  • Manually ensure that all classes in a single API have the exact same @Api annotation properties.
  • Use annotation inheritance through Java inheritance. In this inheritance, all classes in a single API inherit the same API configuration from a common @Api-annotated base class.
  • Use annotation inheritance through the @ApiReference annotation on all classes in a single API to have them reference the same API configuration from a common @Api-annotated class.

Using @ApiClass for properties that can differ between classes

To use this feature, you need the following import:

import com.google.api.server.spi.config.ApiClass; 

While all properties in the @Api annotation must match for all classes in an API, you can additionally use the @ApiClass annotation to provides properties that don't need to be exactly the same between classes. For example:

// API methods implemented in this class allow only "clientIdA". @Api(name = "tictactoe", version = "v1") @ApiClass(clientIds = { "clientIdA" }) class TicTacToeA {  } // API methods implemented in this class provide unauthenticated access. @Api(name = "tictactoe", version = "v1") class TicTacToeB {  } 

where TicTacToeA limits access by using a whitelist of client IDs containing the allowed client ID, and TicTacToeB doesn't limit access.

All properties provided by the @ApiClass annotation have an equivalent property in the @Api annotation. Note that the @Api equivalent property acts as the API-wide default. If there is an API-wide default for that same property, specified in @Api, the class-specific @ApiClass property overrides the API-wide default.

The following examples illustrate the overriding of @Api properties by the class-specific @ApiClass equivalents:

// For this class "boards" overrides "games". @Api(name = "tictactoe", version = "v1", resource = "games") @ApiClass(resource = "boards") class TicTacToeBoards {  } // For this class "scores" overrides "games". @Api(name = "tictactoe", version = "v1", resource = "games") @ApiClass(resource = "scores") class TicTacToeScores {  } // For this class, the API-wide default "games" is used as the resource. @Api(name = "tictactoe", version = "v1", resource = "games") class TicTacToeGames {  } 

Annotation inheritance

The @Api and @ApiClass annotation properties can be inherited from from other classes, and individual properties can be overridden either through Java inheritance or @ApiReference inheritance

Using Java inheritance

A class that extends another class with @Api or @ApiClass annotations behaves as if annotated with the same properties. For example:

@Api(name = "tictactoe", version = "v1") class TicTacToeBase {  } // TicTacToeA and TicTacToeB both behave as if they have the same @Api annotation as // TicTacToeBase class TicTacToeA extends TicTacToeBase {  } class TicTacToeB extends TicTacToeBase {  } 

Annotations are only inherited through Java subclassing, not through interface implementation. For example:

@Api(name = "tictactoe", version = "v1") interface TicTacToeBase {  } // Does *not* behave as if annotated. class TicTacToeA implements TicTacToeBase {  } 

As a result, there is no support for any sort of multiple inheritance of frameworks annotations.

Inheritance works for @ApiClass too:

@ApiClass(resource = "boards") class BoardsBase {  } // TicTacToeBoards behaves as if annotated with the @ApiClass from BoardsBase. // Thus, the "resource" property will be "boards". @Api(name = "tictactoe", version = "v1", resource = "scores") class TicTacToeBoards extends BoardsBase {  } 

where TicTacToeBoards inherits the resource property value boards from BoardsBase, thus overriding the resource property setting (scores) in its @Api annotation. Remember that if any class has specified the resource property in the @Api annotation, all of the classes need to specify that same setting in the @Api annotation; this inheritance technique lets you override that @Api property.

Using @ApiReference inheritance

To use this feature, you need the following import:

import com.google.api.server.spi.config.ApiReference; 

The @ApiReference annotation provides an alternate way to specify annotation inheritance. A class that uses @ApiReference to specify another class with @Api or @ApiClass annotations behaves as if annotated with the same properties. For example:

@Api(name = "tictactoe", version = "v1") class TicTacToeBase {  } // TicTacToeA behaves as if it has the same @Api annotation as TicTacToeBase @ApiReference(TicTacToeBase.class) class TicTacToeA {  } 

If both Java inheritance and the @ApiReference are used, the annotations inherit through the @ApiReference annotation only. @Api and @ApiClass annotations on the class inherited through Java inheritance are ignored. For example:

@Api(name = "tictactoe", version = "v1") class TicTacToeBaseA {  } @Api(name = "tictactoe", version = "v2") class TicTacToeBaseB {  } // TicTacToe will behave as if annotated the same as TicTacToeBaseA, not TicTacToeBaseB. // The value of the "version" property will be "v1". @ApiReference(TicTacToeBaseA.class) class TicTacToe extends TicTacToeBaseB {  } 

Overriding inherited configuration

Whether inheriting configuration by using Java inheritance or @ApiReference, you can override the inherited configuration by using a new @Api or @ApiClass annotation. Only configuration properties specified in the new annotation are overridden. Properties that are unspecified are still inherited. For example:

@Api(name = "tictactoe", version = "v2") class TicTacToe {  } // Checkers will behave as if annotated with name = "checkers" and version = "v2" @Api(name = "checkers") class Checkers extends TicTacToe {  } 

Overriding inheritance works for @ApiClass too:

@Api(name = "tictactoe", version = "v1") @ApiClass(resource = "boards", clientIds = { "c1" }) class Boards {  } // Scores will behave as if annotated with resource = "scores" and clientIds = { "c1" } @ApiClass(resource = "scores") class Scores {  } 

Overriding also works when inheriting through @ApiReference:

@Api(name = "tictactoe", version = "v2") class TicTacToe {  } // Checkers will behave as if annotated with name = "checkers" and version = "v2" @ApiReference(TicTacToe.class) @Api(name = "checkers") class Checkers {  } 

Inheriting @ApiMethod annotations

The @ApiMethod annotation can be inherited from overridden methods. For example:

class TicTacToeBase {  @ApiMethod(httpMethod = "POST")  public Game setGame(Game game) {  } } @Api(name = "tictactoe", version = "v1") class TicTacToe extends TicTacToeBase {  // setGame behaves as if annotated with the @ApiMethod from TicTacToeBase.setGame.  // Thus the "httpMethod" property will be "POST".  @Override  public Game setGame(Game game) {  } } 

Similarly to @Api and @ApiClass annotation inheritance, if multiple methods overriding each other have @ApiMethod annotations, individual properties can be overridden. For example:

class TicTacToeBase {  @ApiMethod(httpMethod = "POST", clientIds = { "c1" })  public Game setGame(Game game) {  } } @Api(name = "tictactoe", version = "v1") class TicTacToe extends TicTacToeBase {  // setGame behaves as if annotated with httpMethod = "GET" and clientIds = { "c1"}.  @ApiMethod(httpMethod = "GET")  @Override  public Game setGame(Game game) {  } } 

There is no @ApiReference annotation or equivalent for methods, so @ApiMethod is always inherited through Java inheritance, not through @ApiReference.

Inheritance and precedence rules

To synopsize the preceding discussion, the follow table shows the inheritance rules and order of precedence.

Annotation/inheritance Rule
@Api Must be identical for all classes.
@ApiClass Specified for a class to override @Api properties.
Java inheritance Class inherits @Api and @ApiClass of base class.
@ApiReference Class inherits @Api and @ApiClass of referenced class.
Using @ApiReference on a class (Java) inheriting from a base class Class inherits the @Api and @ApiClass of referenced class, not from the base class.

Common use cases for annotation inheritance

The following are examples of the typical use cases for inheritance:

For API versioning:

@Api(name = "tictactoe", version = "v1") class TicTacToeV1 {  } @Api(version = "v2") class TicTacToeV2 extends TicTacToeV1 {  } 

For multiclass APIs:

@Api(name = "tictactoe", version = "v1") class TicTacToeBase {} @ApiClass(resource = "boards") class TicTacToeBoards extends TicTacToeBase {  } @ApiClass(resource = "scores") class TicTacToeScores extends TicTacToeBase {  } 

For testing different versions of the same API:

@Api(name = "tictactoe", version = "v1") class TicTacToe {  protected Foo someMethod() {  // Do something real;  }  public Foo getFoo() {  } } @Api(version="v1test") class TicTacToeTest extends TicTacToe {  protected Foo someMethod() {  // Stub out real action;  } } 

where someMethod might return pre-determined responses, avoid calls with side-effects, skip a network or datastore request, and so forth.

Adding the classes to web.xml

After annotating your classes, you must add them to your web.xml file. The following example shows a single class:

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee  http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"  version="3.1">  <!-- Wrap the backend with Endpoints Frameworks v2. -->  <servlet>  <servlet-name>EndpointsServlet</servlet-name>  <servlet-class>com.google.api.server.spi.EndpointsServlet</servlet-class>  <init-param>  <param-name>services</param-name>  <param-value>com.example.skeleton.MyApi</param-value>  </init-param>  </servlet>  <!-- Route API method requests to the backend. -->  <servlet-mapping>  <servlet-name>EndpointsServlet</servlet-name>  <url-pattern>/_ah/api/*</url-pattern>  </servlet-mapping> </web-app>

To add multiple classes:

  1. Replace <param-value>com.example.skeleton.MyApi</param-value> with your own API class name.

  2. Add each class inside this same <param-value> field separated by a comma, for example:

    <param-value>com.example-company.example-api.Hello,com.example-company.example-api.Goodbye</param-value>