For the Java platform, the Apache Olingo project offers a library useful for implementing an OData service. It provides services such as URL parsing, input validation, (de-)serialization of content, request dispatching, etc., according to the OData specification.
The main parts of an OData service implementation are the metadata definition, the run-time processing of requests, and the definition of the web infrastructure. These parts will now be described in more detail.
The Entity Data Model (EDM) is the underlying metadata model of the OData protocol. Within the EDM the following (main) elements are described:
A proper OData Service requires a valid and consistent EDM. In order to speed up performance, the OData Library does no validation of the EDM.
The standard way of defining the metadata is to write code in so-called EDM provider classes. It would be possible to add other sources for metadata, e.g., a predefined metadata document.
EDM provider classes separate the run-time EDM object instances from code that defines OData EDM objects. All provider classes simply define string values for the different EDM elements. At run-time, objects are created only as far as necessary. Since the typical request (apart from the request for the metadata document, of course) can be processed with only the directly involved EDM objects, this can be a significant performance improvement. Furthermore, this solves the problem that many EDM objects depend on each other, making it no easy task to find the right order of object instantiation.
Service implementations can derive from
override only those methods that should provide EDM objects. Almost all
provider methods have a parameter of type
FullQualifiedName that specifies
for which (namespace-qualified) name a provider-object instance is to be
returned. In addition, there are some methods with the purpose of retrieving
a list of all names of a given object type; these are used only for the
output of the metadata document.
The OData standard describes many different types of requests that have to be answered by an OData service implementation. It would not be useful to have a single processing interface or even a single method that a service implementation has to implement. Different designs are possible how the multitude of requests can be split into more manageable parts.
This library has been designed to handle a given OData request in a single
call to a processing method, including the complete query-options part. Which
method the request dispatcher calls is decided according to the HTTP method
and the representation type of the expected response. The HTTP method
describes the fundamental type of operation:
GET for read requests,
PATCH for update requests,
POST for create requests, and
deletion requests. The representation type describes which information can be
retrieved from the OData service: an Entity representation, or only a link
URL, or even just a simple number as a count of entities.
The fact that a single method call occurs for a given request, as complicated
as it may be, leads immediately to the consequence that almost all processing
methods must be prepared to handle things like navigation and system query
options. Navigation in turn means that even if the response in the end may be
a simple count, the implementation may have to read entity collections, their
relations, and even function imports or bound functions that may occur before
$count in the request URI. Services therefore have to be
implemented carefully in order to respond to requests they are not prepared
to handle with a
Not Implemented response and its corresponding HTTP status
code of 501.
A major advantage of the one-step approach is that the processing interfaces don't have to make any assumptions how application data is to be transported. The serialization and deserialization helper methods of the library of course use library-defined data objects but service implementations are free to use their own code for that tasks and still can use the core run-time library functionality.
Many implementations will re-use large parts of code for essentially the same requests that differ only in the returned representation. But there might be optimizations: An SQL request could be much more efficient for count determination if not all entities are retrieved first. Since this library does not favor a specific implementation, the interface is designed to allow the necessary flexibility.
Processor classes have to be registered in order to be called from the
library core at run-time. The core run-time registers the
API class with default implementations of
ErrorProcessor, but this can be overridden
simply by registering an own implementation.
Each class can implement one or more of the processor interfaces; all processing methods have unique names across all processor interfaces.
Since the general design of the library is to have as little constraints as possible, as little as possible happens automatically. The service developer is responsible for the correct response to a given request. There are several helper methods to ease this task considerably. But if you are not satisfied with them, it is always possible to use your own implementation, even if that does not conform to the OData standard.
All processor interfaces derive from the
Processor interface which defines
init() method allowing to get a run-time instance of the
object and the
OData object is the root object for
serving factory tasks and the single instance connecting the API interfaces
with their implementations; each thread (processing of one request) should
keep its own instance. The
ServiceMetadata instance contains the Entity
Data Model and some other objects related to metadata.
All processing methods have at least
objects as parameters. They are supposed to find all necessary information
about the request body and its headers in the request object and set the
corresponding information in the response object.
Processing methods that are not called with a static URI like
additionally have a
UriInfo parameter describing the request URI.
Processing methods that have to read request-body content additionally have a
ContentType parameter describing the request-body format in order to select
the correct deserializer.
Processing methods that have to deliver content in the response body
additionally have a
ContentType parameter describing the requested format
in order to select the correct serializer.
An example with all parameters mentioned above is the method
in the interface
void createEntity(ODataRequest request, ODataResponse response, UriInfo uriInfo, ContentType requestFormat, ContentType responseFormat) throws ODataApplicationException, ODataLibraryException;
Requests that send data must provide a
Content-Type HTTP header.
This is checked in the core run-time code. For each representation type
there is a list of content types that are supported by the implementation.
modifySupportedContentTypes() of the interface
CustomContentTypeSupport can be implemented to change that; it is even
possible to remove library-supported content types. Any implementation of
this interface has to be registered in the same way as processor classes in
order to have any effect.
Request content types that do not match the supported content types are rejected with error messages and the HTTP status code 406 (Not Acceptable).
A request for a response with a content type not matching the supported content types is rejected with an error message and the HTTP status code 415 (Unsupported Media Type).
The list of supported content types can be modified as described for the request content type. It is not possible to have different support for responses than for requests.
All processing methods can and should throw exceptions if something went wrong.
Almost all library utility methods declare exceptions that derive from
ODataLibraryException. Those exceptions are handled by the core run-time
code which takes care of setting the correct response status code and error
If a service implementation wants to signal an error itself, it can throw an
ODataApplicationException. The constructor of this exception allows to
define the response status code and the error text. Please note that the
OData standard mandates specific status codes in many places; this is not
enforced by the library.
RuntimeException occurs, the core run-time code sets the response
status code to 500 (Internal Server Error) and delivers a corresponding error
Service implementations have to set the response status code explicitly. Failing to do so results in a status code of 500 (Internal Server Error). Please note that the OData standard mandates specific status codes in many places; this is not enforced by the library.
For some representation types the OData standard defines different representations, e.g., JSON and XML. For the JSON format there are different sub-formats differing in the amount of metadata that is part of the content.
In order to have a uniform interface and to relieve the service
implementations from many switch statements, it is possible to get the
correct serializer from the
createSerializer() method that
has a content type as parameter. The requested response content type is
passed as parameter to all processing methods that are supposed to create
ODataSerializer interface has methods to serialize the following types
Every data serializer gets its run-time data as an instance of a type defined
commons.api.data package. It has also an additional parameter to
pass options like the context URL or expand settings in a single object.
Please note that according to the OData standard the context URL is mandatory
for service responses except for responses with no metadata at all.
Fixed-format serializers can be used for formats defined in the OData
standard that are not subject to content negotiation. The
FixedFormatSerializer instance to be got from the
createFixedFormatSerializer() method has methods for serializing a binary,
a count, a primitive raw value, a batch, and an asynchronous response.
Deserialization works along the same principles as serialization, with minor differences, however.
There is no deserializer for the representation types service document, metadata document, and error document. There is an additional representation type that can be deserialized: action parameters.
All content-type-dependent methods don't return data objects directly but
DeserializerResult instead. This makes it possible to return
additional information like the expand information.
The context URL is a mandatory part of all data-related responses of an OData service except for responses with no metadata at all. In some cases it cannot be determined from the data alone that is passed to the serialization methods. Therefore, the serialization methods expect the correct context URL as parameter.
ContextURL object has its own builder that allows to build the context
URL from its parts. For the difficult parts the
UriHelper instance to be
got from the
createUriHelper() method has helper methods
that assist with building select lists and key predicates.
UriHelper instance to be got from the
createUriHelper() method has a helper method to construct the canonical URL
of an entity. This URL can be used as
Location HTTP header, for example.
UriHelper instance to be got from the
createUriHelper() method has a helper method to parse the entity-ID URI of
an entity. This method can be used in the handling of reference-changing
The OData standard defines optimistic concurrency control, a mechanism to ensure that a modification request accesses the current version of the data to be modified. Furthermore, this mechanism can be used to enable client-side caching, using the information that the requested data are still current.
To achieve this, OData uses weak entity tags via the
ETag HTTP response
header and its associated
If-None-Match HTTP request
To enable this functionality for run-time data, service implementations can
register a class that implements the
CustomETagSupport interface with its
two methods for entities and media-entity data. These methods should return
for a given entity-set or singleton name whether the service supports entity
tags for entities out of this entity set or singleton. No finer granularity
is possible currently. Processing methods still have to check the ETag(s)
ETagHelper instance to be got from the
createETagHelper() method has two ready-made methods for read and change
To enable this functionality for metadata, i.e., for the service document and
the metadata document, service implementations can pass an instance of an
implementation of the
ServiceMetadataETagSupport interface to the
ServiceMetadata creation described above. The default processors for
service- and metadata document requests already take this ETag support into
Some aspects of OData request processing can be influenced from a client by
setting preferences in the HTTP
Prefer header. The service implementation
is not obliged to honor these preferences. If it does, it should respond with
Preference-Applied HTTP header.
Preferences instance to be got from the
createPreferences() method with the HTTP
Prefer headers as parameter
has named access methods for the preferences defined in the OData standard
plus generic access to other preferences.
PreferencesApplied API class has a builder that helps building a
Preference-Applied HTTP response header.
For support purposes there is a possibility to enrich the service response with additional data helpful for finding bugs. Please note that this information could also help attackers.
To enable this functionality, service implementations can register a class
that implements the
DebugSupport interface with its two methods for user
authorization and creation of output.
DefaultDebugSupport class is already provided where all users
are always authorized and the additional data consists of information about
the request, the response, the parsed request URI, the server environment,
library timings, and the stacktrace in case an error occurred, in a
self-contained HTML document ready for browser usage or in a JSON document.
createDebugResponseHelper() method returns a
DebugResponseHelper instance which is used in this default implementation.
To request the debug output for a request to the OData service the query parameter
odata-debug=html must be appended to the original request URL
http://localhost:8080/odata-server-tecsvc/odata.svc/?odata-debug=html for a local published test service).
To enable an OData service on a web server, the service is wrapped by a web application.
The web application is defined in a
web.xml file where a servlet is
registered. The servlet is a standard
HttpServlet configured to dispatch
all requests to URLs below the service's root URL to the OData handler class.
This is done by overriding the servlet's
service() method. The library
ODataHttpHandler object, creatable from the
OData API class
with the EDM definition as parameter, that can be used inside the
It receives the request and delegates it to the processor implementation of
the OData service if the URL conforms to the OData specification. This means
that all processor implementations of the OData service have to be registered
register() method. The responses of the registered handlers are
given back to the servlet infrastructure and will result in corresponding
Copyright © 2013-2021, The Apache Software Foundation
Apache Olingo, Olingo, Apache, the Apache feather, and the Apache Olingo project logo are trademarks of the Apache Software Foundation.