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:
- Give each class the same
nameandversionstrings in their@Apiannotation. - Add class APIs as a comma-separated list in
web.xml.
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
@Apiannotation 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
@ApiReferenceannotation 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:
To add multiple classes:
Replace
<param-value>com.example.skeleton.MyApi</param-value>with your own API class name.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>