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
name
andversion
strings in their@Api
annotation . - 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
@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:
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 >