SOS Contributor Guide
Introduction
This is the developer guide for the SOS.
To all contributors:
If you contributing code, then please follow these best practice documents:
Build status
Jenkins: |
|
Travis: |
|
Code
GitHub URLs
Glossary
Reference to
SensorWebForDummies.
- Phenomenon
- see Observed Property
- Observed Property
- a property that is observed by a sensor at a feature of interest
- Procedure
- see Sensor
- Sensor
- something that can perform observations
Coordinates
All coordinates within the SOS shall have the specified order. To get the axis order for your srs, your can use this registry
http://www.epsg-registry.org/. If the datasource requires a different order, this shall be established within the Handlers or DAOs, e.g. for features in the
FeatureQueryHandler
implementation.
Admin/Installer interface
work in progress
During the 2012 Google Summer of Code an Installer and Administrator Webinterface was introduced to the 52N SOS (for previous developments see
SosInstallAdminTestbed)
Architecture
- The webapp uses the Spring MVC framework.
- All configurations settings are moved to the database table
global_settings
. Only the database configuration is persisted in a properties file at WEB-INF/config/datasource.properties
.
- The application uses
WEB-INF/config/datasource.properties
to check whether or not the service is installed
- The controller classes are located in
org.n52.sos.web
and it's subpackages
- The JSP/JSTL views are located in
WEB-INF/views
- Spring configuration (and security configuration) is done in
WEB-INF/applicationContext.xml
and WEB-INF/dispatcher-servlet.xml
Installer
- Installation is a 4 step process
- Index page: welcoming words, ability to upload a previos configuration (as JSON). The extracted properties are loaded into the session and can be revised in the following steps. Database settings and administrator credentials are not loaded
- Database configuration: the database connections settings are chosen and tested. This includes the following:
- existence of driver class
- existence of connection pool class
- existence of JDBC dialect class
- connection testing using a plain JDBC connection
- existence of server and database
- check for valid datasbase credentials
- existence of PostGIS extension
- existence and rights of
spatial_ref_sys
table
- check for exising SOS tables
- currently only the
observation
table is checked
- validation of create/overwrite parameters
- check for ability to create tables
- check for the version of the installed SOS
- currently only a warning is logged if versions differ
- Settings: Service Identification/Service Provider/miscellaneous settings can be altered
- allows the user to configure the SOS
- settings will only be roughly validated (existence and format (e.g. integer values))
- Finish: Administrator credentials are chosen
- The previous steps didn't modify anything
- Every action takes place here:
- Validate credentials
- Create tables (if necessary)
- Insert test data (if necessary)
- Insert settings
- Save version to database
- Save admin user
- Instantiate
Configurator
- Save
datasource.properties
- Save installation date to
meta.properties
- Every access to the installer is blocked by
org.n52.sos.web.install.InstallFilter
if the datasource.properties
file is written
- Default installation settings are read from
-
WEB-INF/static/conf/default-database-values.json
-
WEB-INF/static/conf/sos-settings.json
Administrator
Test Client
- The test client was completly rewritten to embed it into the new webapp
- Example requests are defined in
WEB-INF/static/conf/client-requests.json
- The file has the following format:
{
"service":,
"version":,
"binding": ,
"operation":,
"title":,
"headers":{
"Accept":
},
"method":
}
-
binding
values are : application/x-kvp, application/soap+xml, application/xml, application/json
- To define a
POST
request the request
property is used. The value is a path pointing to a file that contains the request. {
"request": "static/examples/sos_v20/requests_soap/Transactional/InsertObservation_Category.xml",
"service": ...
}
- To define a
GET
request the param
property is used. Every JSON property is used as a query parameters (and getting URL encoded). Arrays will be merged to a comma seperated list. The folling results in the query /kvp?service=SOS&version=2.0.0&request=GetFeatureOfInterest&featureOfInterest=test_feature_1,test_feature_2
{
"param": {
"service": "SOS",
"version": "2.0.0",
"request": "GetFeatureOfInterest",
"featureOfInterest": [
"test_feature_1",
"test_feature_2"
]
},
"service": ...
}
- Requests are disabled/enabled depending on which versions/bindings (identified by the path) and operations are supported by the SOS. This is accomplished by requesting the corresponding operators from the
Configurator
.
Extension Points
Datasources
The Installer is able to perform configurations based on any datasource. Every datasource has to implement the interface
org.n52.sos.ds.Datasource
(or for Hibernate based datasources extend the class
org.n52.sos.ds.datasource.AbstractHibernateDatasource
in
hibernate-common
). Datasources are loaded with the Java
ServiceLoader
API and are used to configure and validate the datasource backend of the SOS and to provide functionalities for the administrator interface. It is possible to create datasources for any type of backend, ranging from databases to SOAP services.
- Declaration of settings that the admin/installer will present to the user
- Validation of settings and prerequisites or a schema if needed
- Validation of changes to a existing configuration
- Creation of a
datasource.properties
to initialiize the SOS
- Creation and removal of a schema (if needed)
- Creation and removal of a test data set (if supported)
- Removal of all data (if supported)
Database
DBMS
PostgreSQL/PostGIS
pgAdmin
If you display the values from the observation table, the timestamps (without time zone) are located in UTC, as they stored in the database.
MySQL
MySQL Workbench
If you display the values from the observation table, the timestamps are converted to system/default time zone, and they are not shown as they are stored in the database (UTC).
Extensions
- Encoders return XmlBeans *Type and not *Document objects
- HibernateException extends RuntimeException!!!
Binding
This section provides some starting points for developing your own binding by implementing the required interfaces from
52n-sos-api
module. But first, what is a binding:
"[A binding] describe[s] how SOS [...] clients and servers can communicate with each other." (
OGC#12-006 Sec. 13)
The specification defines two default bindings:
- KVP - using KVP encoded HTTP GET requests
- SOAP - using XML encoded SOAP requests with HTTP POST
In addition, we have implemented two other bindings:
If you want to develop your own binding, the starting point is the abstract class
org.n52.sos.binding.Binding
with the following interface:
Depending on your binding you need to implement different methods. Within the 52°North SOS, the different bindings are identified by an
URL pattern (e.g.
soap
→ SOAP,
kvp
→ KVP binding). Hence, you can execute a GetCapabilities request using the KVP binding via
[...]WEB_APP/sos/kvp?service=SOS&request=GetCapabilities
. The
getUrlPattern()
method provides this pattern and requires your implementation. Currently, the following patterns are already in use:
It helps us to maintain this list, if you tell us when implementing another binding as open source.
Now, some functionality. It is foreseen to use at least one of following HTTP methods in your binding:
- DELETE
- GET
- OPTIONS
- POST
- PUT
The abstract
Binding
class offers already an implementation for each of these methods returning a HTTP 501 - Not implemented for all methods you don't override. For each HTTP method, a check method can be implemented to tell the SOS logic, that you are handling the current service-version-operation combination with the according HTTP method. Here, the default implementation is
return false
. For more details, checkout the source code and javadoc.
[...]
What is missing here?
If you want to provide your own example requests for the integrated test client, just follow
these steps.
:
Writer's Notes
- exceptions -> own section <- used in Operation section, too.
- exceptions need to be encoded by each binding -> examples, which protected methods are available in abstract class
Binding
Operation
This section provides some starting points for developing your own operation by implementing some interfaces of the
52n-sos-api
module. When doing it right, your new operation shall be usable by all already existing bindings.
For each interface of the
52n-sos-api
module that you implement directly (or indirectly via any
Abstract*
type), you need to provide a so called
ServiceLoader
configuration file, having the same name of the implemented interface and containing a list of qualified names of types implementing this interface (one per line). This file must be located in
src/main/resources/META-INF/services
in the according maven module.
RequestOperator
An request operator is called by the internal service logic to handle a specific received request. In OGC services, the functionality is available in terms of requests (:= operations, e.g. GetCapabilities, GetObservation). First of all, you need to implement the
org.n52.sos.request.operator.RequestOperator
interface which requires the implementation of the following methods:
You shall implement this interface by extending the abstract class
org.n52.sos.request.operator.AbstractRequestOperator<D extends OperationDAO, R extends AbstractServiceRequest>
.
This results in implementing only the
receiveRequest(AbstractServiceRequest request)
method. A generic workflow could look like this
42 public ServiceResponse receiveRequest(MyOperationRequest sosRequest) throws OwsExceptionReport
43 {
44 // doing some parameter checks and throwing a subtype of OwsExceptionReport in the case of any errors
45 checkRequestedParameter(sosRequest);
46
47 // process the request by the DAO
48 MyOperationResponse sosResponse = getDao().myOperation(sosRequest);
49
50 // the next two steps are only required of the content of the SOS is changed by the new operation
51 SosEventBus.fire(new MyOperationEvent(sosRequest, sosResponse));
52 getConfigurator().getCacheController().updateAfterMyOperation();
53
54 // method is trying to get an encoder for the type of the sosResponse object which returns an SosResponse object
55 return encode(sosResponse);
56 }
You might have recognized, that four additional interfaces are now added to our "list of interfaces to be implemented":
-
org.n52.sos.request.operator.RequestOperator
-
org.n52.sos.ds.OperationDAO
-
org.n52.sos.request.AbstractServiceRequest
→ super type of MyOperationRequest
-
org.n52.sos.response.AbstractServiceResponse
→ super type of MyOperationResponse
- optional:
org.n52.sos.event.SosEvent
→ super type of MyOperationEvent
OperationDAO
An
OperationDAO
(DAO :=
Data
Access
Object) provides means to interact with the underlying data source. Hence, the implementation of the
OperationDAO
is data source vendor specific and results in the definition of an operation specific extension of the
OperationDAO
interface, e.g.
MyOperationDAO
. The interface is implemented in vendor specific modules (here: maven module). This makes them easy to exchange (You might make an abstract implementation of the
OperationDAO
to share the implementation of the
OperationDAO.setOperationsMetadata(opsMeta,service,version)
method).
The interface
OperationDAO
defines the following methods:
Using the abstract super type
org.n52.sos.ds.AbstractOperationDAO
for your implementation will reduce the number of methods to implement:
23 public abstract class MyOperationDAO extends AbstractOperationDAO
24 {
25
26 public MyOperationDAO()
27 {
28 super(MyOperationConstants.OPERATION_NAME);
29 }
30
31 @Override
32 protected void setOperationsMetadata(OWSOperation opsMeta, String service, String version) throws OwsExceptionReport
33 {
34 // adding some allowed values to the operations metadata section of the GetCapabilities response
35 opsMeta.addPossibleValuesParameter(PARAMETER_NAME, getCache().getObservationIdentifiers());
36 [...]
37 }
38
39 /**
40 * My operation does something urgently required by each SOS user
41 * [...]
42 * Only this method needs to be implemented by the data source vendor specific implementations.
43 */
44 public abstract MyOperationResponse myOperation(MyOperationRequest myOperationRequest) throws OwsExceptionReport;
45 }
Our "list of interfaces to be implemented" looks like this (I am leaving out the implementation of
!MyOperationDAO
, you might remember it on your own):
-
org.n52.sos.request.operator.RequestOperator
-
org.n52.sos.ds.OperationDAO
-
org.n52.sos.request.AbstractServiceRequest
→ super type of MyOperationRequest
-
org.n52.sos.response.AbstractServiceResponse
→ super type of MyOperationResponse
- optional:
org.n52.sos.event.SosEvent
→ super type of MyOperationEvent
MyOperation{Request|Response}
The
*Request
and
*Response
objects are very generic data holding types. The abstract super types require very less methods to be implemented.
You need to think about what properties are required for performing the new operation and encoding a valuable response. Therefore the abstract type
org.n52.sos.request.AbstractServiceRequest
requires only one method:
42 /**
43 * @return the operationName
44 */
45 public abstract String getOperationName();
A little bit easier is the implementation of the abstract type
org.n52.sos.response.AbstractServiceResponse
. It requires no method at all. Remember, it's
your turn to think, here.
Our "list of interfaces to be implemented" looks like this (I am leaving out the implementation of
!MyOperationDAO
, you might remember it on your own):
-
org.n52.sos.request.operator.RequestOperator
-
org.n52.sos.ds.OperationDAO
-
org.n52.sos.request.AbstractServiceRequest
→ super type of MyOperationRequest
-
org.n52.sos.response.AbstractServiceResponse
→ super type of MyOperationResponse
- optional:
org.n52.sos.event.SosEvent
→ super type of MyOperationEvent
→ optional, therefore not part of this guide.
{De|En}coder
In the previous sections, we were talking about requests and response being java types. In addition, in the context of the SOS there are other types of requests and responses: HTTP-. The internal logic is only capable of the
AbstractService{Request|Response}
types. Hence, we need to provide something that is able to translate between those two: an Encoder and a Decoder. The interfaces are:
-
org.n52.sos.encode.Encoder
-
org.n52.sos.decode.Decoder
In both cases,
S
in the source type and
T
is the target or resulting type.
To identify the correct decoder for the correct request, a
DecoderKey
is required. See here an example for handling incoming
MyOperationDocument
objects.
*Document
types are often used by XMLbeans, for example. Therefor, the decoder with the following key will be able to decode XML encoded MyOperation requests:
42 @SuppressWarnings("unchecked")
43 private static final Set<DecoderKey> DECODER_KEYS = CollectionHelper.union(
44 CodingHelper.decoderKeysForElements(MyOperationConstants.NAME_SPACE, MyOperationDocument.class),
45 CodingHelper.xmlDecoderKeysForOperation(SosConstants.SOS, Sos2Constants.SERVICEVERSION, MyOperationConstants.Operations.MyOperation));
This key tells the SOS, that this decoder will handle SOS 2.0 MyOperation requests of type
!MyOperationDocument
.
T
is
MyOperationRequest
and
S
org.apache.xmlbeans.XmlObject
. The interface requires the following methods to be implemented:
After processing the request, we have to return a response. In the case of exceptions being thrown the SOS logic will do all the required business. Hence, we need to think about the encoding of
good responses, only.
The encoding of the
MyOperationResponse
is done by implementing a
org.n52.sos.encode.Encoder<SosResponse, MyOperationResponse>
including the methods of the interface.
An example for the
ENCODER_KEYS
:
42 @SuppressWarnings("unchecked")
43 private static final Set<EncoderKey> ENCODER_KEYS =
44 CodingHelper.encoderKeysForElements(MyOperationConstants.NAME_SPACE, MyOperationResponse.class);
In the case of implementing a KVP decoder for SOS 2.0, you shall implement the abstract class
org.n52.sos.decode.kvp.v2.AbstractKvpDecoder
and know its interfaces.
Encoder
This section describes the development of a new encoder. Next to the base encoder interface the
specialised sub interfaces and the most important
abstract encoder classes implementations (for JSON and XML) are described. The informatio which Object an encoder can handle are provided in the encoder keys which are described in
last section.
An encoder shall implement the
org.n52.sos.encode.Encoder
interface which defines the required methods for an encoder implementation.
A helpful class for XML encoding is the
Object.CodingHelper
. This class provides some methods to create
EncoderKey
or to encode an
Object
to an XmlObject.
The UML of the
org.n52.sos.encode.Encoder
interface:
Specialized sub interfaces
This section describes specialized sub interfaces of the
org.n52.sos.encode.Encoder
interface. The sub interfaces provide further methods which are used by the service.
ObservationEncoder
The
org.n52.sos.encode.ObservationEncoder
interface defines additional methods for observation encoder and each observation encoder shall implement this interface.
Additional methods are for example to get the responseFormats which is supported by this observation encoder. This interface extends
org.n52.sos.encode.Encoder
with the following methods:
- boolean isObservationAndMeasurmentV20Type()
- necessary for SOS 2.0 GetObservationResponse encoding
- boolean shallObservationsWithSameXBeMerged()
- flag to merge observations with the same procedure, observableProperty and featureOfInterest
- Set<String> getSupportedResponseFormats(String service, String version)
- Get the responseFormats supported by this encoder depending on Service and Version.
The UML of the
org.n52.sos.encode.ObservationEncoder
interface:
ProcedureEncoder
The
org.n52.sos.encode.ProcedureEncoder
interface identifies a procedure encoder and inherits additional methods from the
org.n52.sos.coding.ProcedureCoder
. Each procedure encoder shall implement this interface.
Additional methods are for example to get the procedure description formats which are supported by this encoder.
The UML of the
org.n52.sos.encode.ProcedureCoder
and
org.n52.sos.coding.ProcedureCoder
interface:
The UML of the
org.n52.sos.encode.ProcedureEncoder
interface:
StreamingEncoder
The
org.n52.sos.encode.StreamingEncoder
interface defines additional methods for streaming encoders. With a streaming encoder the 52N SOS writes the data into an
OutputStream
. For example in the case of XML encoding, the 52N SOS can write the response directly into the output stream without creating a full XML document before sending the response.
This interface extends
org.n52.sos.encode.Encoder
with the following methods:
- void encode(S objectToEncode, OutputStream outputStream) throws OwsExceptionReport;
- To encode the objectToEncode directly to the OutputStream
- void encode(S objectToEncode, OutputStream outputStream, EncodingValues encodingValues) throws OwsExceptionReport;
- To encode the objectToEncode directly to the OutputStream and provide some EncodingValues which are required for the encoding.
- boolean forceStreaming(); * Force streaming of the encoding.
The UML of the
org.n52.sos.encode.StreamingEncoder
interface:
Abstract classes
For some encodings the 52N SOS provides abstract classes which implement the
Encoder
interface. The abstract class implements methods which are default for all sub classes and defines the returned object. Examples are encoders for JSON and XML.
JSONEncoder
The abstract
org.n52.sos.encode.json.JSONEncoder
shall be extended for all concrete JSON encoders. It implements methods which provide information that are equal for all JSON encoders, e.g. the contentType, and defines that the returned object is a JsonNode.
The UML of the
org.n52.sos.encode.json.JSONEncoder
class:
AbstractSpecificXmlEncoder
The abstract
org.n52.sos.encode.AbstractSpecificXmlEncoder
implements methods which provide information which are equal for all XML encoders and defines some further methods that are helpful in the sub classes.
The UML of the
org.n52.sos.encode.AbstractSpecificXmlEncoder
class:
AbstractXmlEncoder
The abstract
org.n52.sos.encode.AbstractXmlEncoder
shall be extended for all concrete XML encoder and implements methods that provide information which are equal for all XML encoder, e.g. the content type, and defines the returned object is a XmlObject.
The UML of the
org.n52.sos.encode.AbstractXmlEncoder
class:
To concrete object encoder
Instead of implementing an encoder that extends from the AbstractXmlEncoder you can implement an encoder for a concrete XML type that is returned, e.g. XML Document or PropertyType.
EncoderKey
The information which
Object
an encoder can handle are provided in the
org.n52.sos.encode.EncoderKey
. Each
Encoder
has the
getEncoderKeyType()
method to get the
EncoderKeys
The UML of the
EncoderKey
interfaces and implemented classes:
JSONEncoderKey
The
org.n52.sos.encode.json.JSONEncoderKey
is used by
JSONEncoder
and defines which
Object
can be encoded by the specific JSONEncoder.
An example for the
ENCODER_KEYS
of a JSONEncoder:
@SuppressWarnings("unchecked")
private static final Set<EncoderKey> ENCODER_KEYS =
Sets.newHashSet(new JSONEncoderKey(MyClass.class));
ClassToClassEncoderKey
The
org.n52.sos.encode.ClassToClassEncoderKey
is used to define which
Object
can be encoded to which
Object
, e.g.
OmObservation
(52N SOS) to
OMObservationType
(
XmlBeans).
An example for the
ENCODER_KEYS
:
@SuppressWarnings("unchecked")
private static final Set<EncoderKey> ENCODER_KEYS =
Sets.newHashSet(new ClassToClassEncoderKey(ToEncodeClass.class, MyClass.class));
XmlEncoderKey
The
org.n52.sos.encode.XmlEncoderKey
is used to define which
Object
can be encoded by the XML enocder to
XmlObject
(
XmlBeans).
An example for the
ENCODER_KEYS
:
@SuppressWarnings("unchecked")
private static final Set<EncoderKey> ENCODER_KEYS =
CodingHelper.encoderKeysForElements(namespace, MyClass.class, MyClass2.class);
XmlDocumentEncoderKey
The
org.n52.sos.encode.XmlDocumentEncoderKey
is used to define which
Object
can be encoded to a XML document, e.g.
OmObservation
(52N SOS) to
OMObservationDocument
(
XmlBeans).
An example for the
ENCODER_KEYS
:
@SuppressWarnings("unchecked")
private static final Set<EncoderKey> ENCODER_KEYS =
Sets.newHashSet(new XmlDocumentEncoderKey(namespace, MyClass.class));
XmlPropertyTypeEncoderKey
The
org.n52.sos.encode.XmlPropertyTypeEncoderKey
is used to define which
Object
can be encoded to XML propertyType, e.g.
OmObservation
(52N SOS) to
OMObservationPropertyType
(
XmlBeans).
An example for the
ENCODER_KEYS
:
@SuppressWarnings("unchecked")
private static final Set<EncoderKey> ENCODER_KEYS =
Sets.newHashSet(new XmlPropertyTypeEncoderKey(namespace, MyClass.class));
New Observation types
This chapter describes how to add the support for a new observation type to the 52N SOS.
There are two options:
- Usine an additional database table to store the new result type.
- Use the existing tables and convert the observation to the requested type.
Add Value to API
Observation value class
Define a new observation value class which shall implement the
org.n52.sos.ogc.om.values.Value
interface.
The UML of the
org.n52.sos.ogc.om.values.Value
interface:
For single observation values, your new value class can directly implement the
org.n52.sos.ogc.om.values.Value
interface. If your observation value shall contain multiple single values, you can use the
org.n52.sos.ogc.om.values.MultiValue
interface which extends from
org.n52.sos.ogc.om.values.Value
and contains a list of values. An exmaple for a
MultiValue is the
org.n52.sos.ogc.om.values.TVPValue
class.
Value visitors
Now you have to apply your new observation value class to the value visitor interfaces
org.n52.sos.ogc.om.values.visitor.ValueVisitor
and the
org.n52.sos.ogc.om.values.visitor.VoidValueVisitor
and implement the methods in the classes which implements these visitors.
The UML of the
org.n52.sos.ogc.om.values.visitor.ValueVisitor
interface and
org.n52.sos.ogc.om.values.visitor.VoidValueVisitor
class:
Database tables and Hibernate entities
This section describes how to define a new database table in the Hibernate mapping files and how to add a new Hibernate entity class to the SOS.
Changes to the database tables would be required if it is not possible to store the new observation type in the existing value tables.
Database tables
To add a new observation type to the database model, you have to enhance the
Observation.hbm.xml
and the
ValuedObservation.hbm.xml
files in the
src/main/resources/mapping/series/observation
folder of the
hibernate-mapping
module.
Since the 52N SOS provides further database concepts you shall also enhance the mappings for the other concepts. The following table defines the folder for the other database concepts.
The default concept is the series concept which has a separate series table which is related to the obsevation table. The series table is used to identify all observation for the combination of procedure, observedProperty and featureOfInterest.
The ereporting concept is an enhanced series concepts that fulfills the requirements for the AQD e-Reporting.
The old concept is deprecated and holds the procedure, observedProperty and featureOfInterest information directly in the observation table.
database concept |
mapping path |
old |
src/main/resources/mapping/old/observation |
series |
src/main/resources/mapping/series/observation |
ereporting |
src/main/resources/mapping/ereporting |
There you have to add a new similar to the existing. The new definition shall
extends
the
org.n52.sos.ds.hibernate.entities.observation.series.AbstractSeriesObservation
As an example for a single valued observation the
numericValue
table definition can be used for orientation. For a multi value observation we recommend to have a look at the
complexValue
table definition. In this case the single values of the complex observation are stored as separate observation.
Important notes
- A name of table, column, key, ... shall not be longer than 30 characters.
- A table name shall be in lowercase.
Observation.hbm.xml
The name of the shall be something like this
org.n52.sos.ds.hibernate.entities.observation.series.full.SeriesMyTypeObservation
in the Observation.hbm.xml.
Here you can find the example for your new observation.
...
<joined-subclass name="org.n52.sos.ds.hibernate.entities.observation.series.full.SeriesMyTypeObservation"
extends="org.n52.sos.ds.hibernate.entities.observation.series.AbstractSeriesObservation"
table="mytypevalue">
<comment>Value table for MyType observation</comment>
<key foreign-key="observationMyTypValueFk">
<column name="observationId" >
<comment>Foreign Key (FK) to the related observation from the observation table. Contains "observation".observationid</comment>
</column>
</key>
<property name="value"
type="MyType">
<column name="value">
<comment>MyType observation value</comment>
</column>
</property>
</joined-subclass>
...
ValuedObservation.hbm.xml
The name of the shall be something like this
org.n52.sos.ds.hibernate.entities.observation.series.valued.MyTypeValuedSeriesObservation
in the ValuedObservation.hbm.xml.
Here you can find the example for your new valued observation.
...
<joined-subclass name="org.n52.sos.ds.hibernate.entities.observation.series.valued.MyTypeValuedSeriesObservation"
extends="org.n52.sos.ds.hibernate.entities.observation.series.AbstractValuedSeriesObservation"
table="mytypevalue">
<comment>Value table for MyType observation</comment>
<key foreign-key="observationMyType ValueFk">
<column name="observationId" >
<comment>Foreign Key (FK) to the related observation from the observation table. Contains "observation".observationid</comment>
</column>
</key>
<property name="value"
type="MyType ">
<column name="value">
<comment>MyType observation value</comment>
</column>
</property>
</joined-subclass>
...
Hibernate entities
The corresponding Hibernate entities shall be added to the
hibernate-common
module. As a minimum you have two add two interfaces and two classes that implement the interfaces. One interface and class is for
ValuedObservation
interface and the others for the
Observation
interface.
ValuedObservation interface and class
For your new observation value you have to implement a ValuedObservation interface and a class for the new observation type.
The UML of the
org.n52.sos.ds.hibernate.entities.observation.valued.ValuedObservation
interface:
The definition of the interface for the new observation value shall look like this:
package org.n52.sos.ds.hibernate.entities.observation.valued;
public interface MyTypeValuedObservation extends ValuedObservation<MyType> {
// add here the additional method definition for your type
}
The class implementation of the new observation shall look like this:
package org.n52.sos.ds.hibernate.entities.observation.series.valued;
public class MyTypeValuedSeriesObservation extends AbstractValuedSeriesObservation<MyType>
implements MyTypeValuedObservation {
}
The UML of the
org.n52.sos.ds.hibernate.entities.observation.series.valued.AbstractValuedSeriesObservation
class:
The class implementation example is for the series database concept. In the following table you can find the relevant classes for the other database concepts.
database concept |
super class |
old |
org.n52.sos.ds.hibernate.entities.observation.legacy.AbstractValuedLegacyObservation |
series |
org.n52.sos.ds.hibernate.entities.observation.series.AbstractValuedSeriesObservation |
ereporting |
org.n52.sos.ds.hibernate.entities.observation.ereporting.AbstractValuedEReportingObservation |
Observation interface and class
For your new observation value you have to implement an Observation interface and a class for the new observation type.
The definition of the interface for the new observation shall look like this:
package org.n52.sos.ds.hibernate.entities.observation.full;
public interface MyTypeObservation extends MyTypeValuedObservation, Observation<MyType> {
}
The class implementation of the new observation shall look like this:
package org.n52.sos.ds.hibernate.entities.observation.series.full;
public class SeriesMyTypeObservation extends AbstractSeriesObservation<MyType>
implements MyTypeObservation {
}
The UML of the
org.n52.sos.ds.hibernate.entities.observation.series.AbstractSeriesObservation
class:
The class implementation example is for the series database concept. In the following table you can find the relevant classes for the other database concepts.
database concept |
super class |
old |
org.n52.sos.ds.hibernate.entities.observation.legacy.AbstractLegacyObservation |
series |
org.n52.sos.ds.hibernate.entities.observation.series.AbstractSeriesObservation |
ereporting |
org.n52.sos.ds.hibernate.entities.observation.ereporting.AbstractEReportingObservation |
Visitor interfaces
Finally, you have to add your new
ValuedObservation
and
Observation
to the following interfaces:
-
org.n52.sos.ds.hibernate.entities.observation.ValuedObservationVisitor
-
org.n52.sos.ds.hibernate.entities.observation.VoidValuedObservationVisitor
-
org.n52.sos.ds.hibernate.entities.observation.ObservationVisitor
-
org.n52.sos.ds.hibernate.entities.observation.VoidObservationVisitor
And in the implementations of these interfaces you have to implement the new methods. Currently, the following classes implements these interfaces:
-
org.n52.sos.ds.hibernate.util.observation.ObservationValueCreator
-
org.n52.sos.ds.hibernate.util.observation.SweAbstractDataComponentCreator
Converter
Another approach, without creating new database tables, is to convert the created observation to the requested. Here the 52N SOS creates the default internal representation of the observation from the database tables and in the converter the observation is converted to the requested observation type.
RequestResponseModifier
The converter shall be an implementation of the
org.n52.sos.convert.RequestResponseModifier
interface which is listed below. To load this converter, the
ServiceLoader
concept is also used and you have to define a
org.n52.sos.convert.RequestResponseModifier
file in the
src/main/resources/META-INF/services
that contains the package and class name of the implemented converter.
The UML of the
org.n52.sos.convert.RequestResponseModifier
interface:
RequestResponseModifierFacilitator
The
org.n52.sos.convert.RequestResponseModifierFacilitator
is an indicator for the logic of the 52N SOS in which order the
RequestResponseModifier shall be executed.
The UML of the
org.n52.sos.convert.RequestResponseModifierFacilitator
class:
The indicators are:
indicator |
description |
merger |
Merges single observations to an observation that contains the values, e.g. single values to OM_SweArrayObservation |
splitter |
Splits the values of an observation into single observation, e.g. a OM_SweArrayObservation into single OM_Measurements |
adderRemover |
Adds or removes values to/from the response/request. E.g. the FlexibleIdentifierModifier |
ObservationMerger
The
org.n52.sos.ogc.om.ObservationMerger
class merges single observations to an observation with teh same indicators which are defined in the
org.n52.sos.ogc.om.ObservationMergeIndicator
.
The UML of the
org.n52.sos.ogc.om.ObservationMergeIndicator
class:
ObservationMergeIndicator
The
org.n52.sos.ogc.om.ObservationMergeIndicator
defines the objects which shall be the same in the observations that shall be merged.
The UML of the
org.n52.sos.ogc.om.ObservationMergeIndicator
class:
indicator |
description |
procedure |
Merges all observations with the same procedure |
observableProperty |
Merges all observations with the same observableProperty |
featureOfInterest |
Merges all observations with the same featureOfInterest |
offerings |
Merges all observations with the same offering |
phenomenonTime |
Merges all observations with the same phenomenonTime |
resultTime |
Merges all observations with the same resultTime |
samplingGeometry |
Merges all observations with the same samplingGeometry |
observationConstellation |
Merges all observations with the same combination of procedure, observableProperty, featureOfInterest and offerings. This is the default of the static method defaultObservationMergerIndicator() |
Example implementation of a converter
Since the 52N SOS 4.4 you can find the
org.n52.sos.converter.InspireObservationResponseConverter
in the
inspire-code
module.
Implement a new Encoder
See
Encoder documentation
Features
Exceptions
If you need to throw own exceptions, please take a look at the org.n52.sos.ogc.ows.OwsExceptionReport hierarchie and according packages in the api module:
Capabilities Cache
- Cache creation and updating is managed by
CapabilitiesCacheController
- At startup, the cache controller initializes the cache creation
- The cache stores the following information
- Offerings
- Resources
- Relations between resources
Updating
- Current procedure uses always the database for updating and creation
- Updates are caused by
- Timer (@see: configuration property
CAPABILITIES_CACHE_UPDATE_INTERVAL
[in minutes])
- Transactional operations:
- InsertObservation
- InsertSensor
- DeleteSensor
- DeleteObservation (→ is an not published extension)
- InsertResultTemplate
- DB cache update actions class hierarchy:
Setting Management
Declaring Settings
To let the SOS recognize Settings the interface
org.n52.sos.config.SettingDefinitionProvider
has to be implemented. It provides
SettingDefinitions
that are used to generate the user interface and to save settings in a type safe manner. Currently settings for
File
,
Boolean
,
Integer
,
Double
,
String
and
URI
are implemented (see the corresponding
SettingDefintion
subclasses in
org.n52.sos.config.settings
. Settings are identified by a key which has to be unique. If you're developing an extension use a vendor prefix to be sure you don't get a conflict with other settings. The =SettingDefinitionProvider=s are loaded with the Java ServiceLoader API, so be sure to declare your provider. It is important that providers are not classes that are loaded by the configurator as they have to be instantiated earlier in the initialization process of the service.
Example for a setting provider:
package org.n52.sos.extension;
import java.util.Collections;
import java.util.Set;
import org.n52.sos.config.SettingDefinition;
import org.n52.sos.config.SettingDefinitionProvider;
import org.n52.sos.config.settings.IntegerSettingDefinition;
import org.n52.sos.service.ServiceSettings;
public class ExtensionSettings implements SettingDefinitionProvider {
public static final String SETTING_KEY = "extension.aSetting";
public static final IntegerSettingDefinition SETTING_DEFINITION = new IntegerSettingDefinition()
.setGroup(ServiceSettings.GROUP)
.setOrder(8)
.setKey(SETTING_KEY)
.setMinimum(1)
.setDefaultValue(5)
.setTitle("The readable title of the setting")
.setDescription("This is a description text for the user.");
@Override
public Set<SettingDefinition<?, ?>> getSettingDefinitions() {
return Collections.singleton(SETTING_DEFINITION)
}
}
Settings are grouped in tabs for the UI. These groups can be configured by setting the
group
property of the setting definition to a appropriate
SettingDefinitionGroup
. You can use the predefined groups or create a new group. Existing groups are:
-
org.n52.sos.ogc.ows.SosServiceIdentificationFactorySettings.GROUP
-
org.n52.sos.ogc.ows.SosServiceProviderFactorySettings.GROUP
-
org.n52.sos.service.MiscSettings.GROUP
-
org.n52.sos.service.ServiceSettings.GROUP
Groups and settings are orderd in the UI to provide a consistent view of the settings. Both
SettingDefinition
and
SettingDefinitionGroup
are implenting
Ordered
to provode such a ordering based on
floats
. This way additional settings can be inserted at any place in the UI.
Using settings
Settings can be requested, modified and deleted with the corresponding methods in
org.n52.sos.config.SettingsManager
. But most classes shalln't use the
SettingsManager
directly as settings can be injected directly into your instances. This works for all classes loaded by the
Configurator
, a subclass of
AbstractConfiguringServiceLoaderRepository
or a
ConfiguringSingletonServiceLoader
. These classes are automatically registered at the SettingsManager but you also can register your object instance manually by calling
SettingsManager.getInstance().configure(object)
.
The injecting of settings is annotation based. Classes that want to receive settings have to be annotated with
org.n52.sos.config.annotation.Configurable
. Setters for settings have to be annotated with
org.n52.sos.config.annotation.Setting
with the corresponding setting key as an argument. Setters are expected to throw an
ConfigurationException
if the supplied value is invalid, be sure to include a human readable error message as the exception is forwarded to the user. If an exception is thrown the old value of the setting will be reapplied.
Example for a setting receiving class:
package org.n52.sos.extension;
import org.n52.sos.config.annotation.Configurable;
import org.n52.sos.config.annotation.Setting;
import org.n52.sos.service.ConfigurationException;
import org.n52.sos.util.Validation;
@Configurable
public abstract class SettingReceiver {
private int setting;
@Setting(ExtensionSettings.SETTING_KEY)
public void setUpdateInterval(int setting) throws ConfigurationException {
Validation.greaterZero("Extension Setting", setting);
this.setting = setting;
}
}
Once registered the instance receives updates of the setting everytime it changes but it won't prevent the finalization of the instance by the garbage collector.
Testing
This section describes how to write tests (JUnit) and use the SOS concepts in the tests.
De-/Encoder
To initiate the de-/encoders you have to add this to your test class:
1 @BeforeClass
2 public final static void initDecoders() {
3 CodingRepository.getInstance();
4 }
Settings Managment
For some tests a SettingsManager implementation is required. The
sqlite-config
SOS module/arartifact provides a SettingsManager for testing (
org.n52.sos.config.sqlite.SQLiteSettingsManagerForTesting
) which can be used.
If the a SettingsManager implementation is required, add these dependencies to the module pom.xml file:
1 <dependency>
2 <groupId>${project.groupId}</groupId>
3 <artifactId>test</artifactId>
4 <scope>test</scope>
5 </dependency>
6 <dependency>
7 <groupId>${project.groupId}</groupId>
8 <artifactId>sqlite-config</artifactId>
9 <scope>test</scope>
10 </dependency>
11 <dependency>
12 <groupId>${project.groupId}</groupId>
13 <artifactId>sqlite-config</artifactId>
14 <type>test-jar</type>
15 <scope>test</scope>
16 </dependency>
In some cases it is required to instantiate (BeforeClass) and cleanup (AfterClass) the SettingsManager implementation:
1 @BeforeClass
2 public static void initSettingsManager() {
3 SettingsManager.getInstance();
4 }
5
6 @AfterClass
7 public static void cleanupSettingManager() {
8 SettingsManager.getInstance().cleanup();
9 }
In certain cases custom builds of the SOS may want to include extra content in the webapp (for instance extra admin menu items or title content). To facilitate this, the main build can check for the existence of optional jsp files and include them if present. Custom builds can create these custom jsp files to add additional content.
Path |
Content |
WEB-INF/views/common/extra-admin-menu-items.jsp |
Add extra menu items to the admin menu (use <li> list items) |
WEB-INF/views/common/exta-title.jsp |
Add extra title content (on every page) above normal title |
Development environment
Tomcat context
Instead of building and deploying a .war-file to test your SOS you can also set the context of a webapp to the target folder of the 52n-sos-webapp module within a Tomcat installation of the development machine. Then you only have to package the project to update the application.
During the development, the following steps will update the application.
- Stop Tomcat
- Run
mvn package
(optionally with -DskipTests
and -Pdevelop
)
- Start Tomcat
Remote Debugging
In the documentation wiki you can find
Eclipse tips and including remote debugging