attach-javadocs
@@ -86,19 +87,18 @@
com.squareup.okhttp3
okhttp
- 3.6.0
+ 4.12.0
com.fasterxml.jackson.core
jackson-databind
- 2.12.7.1
+ 2.18.2
- junit
- junit
- 4.13.1
- test
+ org.junit.jupiter
+ junit-jupiter
+ 5.11.4
diff --git a/java/src/main/java/org/opensky/api/OpenSkyApi.java b/java/src/main/java/org/opensky/api/OpenSkyApi.java
index 27fc909..54696b5 100644
--- a/java/src/main/java/org/opensky/api/OpenSkyApi.java
+++ b/java/src/main/java/org/opensky/api/OpenSkyApi.java
@@ -6,6 +6,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import okhttp3.*;
+import org.jetbrains.annotations.NotNull;
import org.opensky.model.OpenSkyStates;
import org.opensky.model.OpenSkyStatesDeserializer;
@@ -13,6 +14,7 @@
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.nio.charset.Charset;
+import java.time.LocalDateTime;
import java.util.*;
/**
@@ -55,6 +57,45 @@ public Response intercept(Chain chain) throws IOException {
}
}
+ /**
+ * This class is an implementation of the {@link Interceptor} interface
+ * used to manage and include the Authorization Bearer Token in HTTP requests.
+ *
+ * It intercepts outgoing HTTP requests and adds an "Authorization" header with
+ * a Bearer token. Tokens are fetched from the provided {@link OpenSkyAuthentication} instance,
+ * and they are cached with an expiration time for reuse to reduce redundant token requests.
+ *
+ * If the token is expired or unavailable, a new token is fetched from the {@link OpenSkyAuthentication}.
+ */
+ private static class AuthBearerTokenInterceptor implements Interceptor {
+
+ private final OpenSkyAuthentication auth;
+ private String token;
+ private LocalDateTime expirationTime;
+
+ AuthBearerTokenInterceptor(OpenSkyAuthentication auth) {
+ this.auth = auth;
+ this.token = "";
+ this.expirationTime = null;
+ }
+
+ @Override
+ public Response intercept(@NotNull Chain chain) throws IOException {
+ LocalDateTime now = LocalDateTime.now();
+
+ if (token.isEmpty() || expirationTime == null || now.isAfter(expirationTime)) {
+ token = auth.accessToken();
+ expirationTime = LocalDateTime.now().plusMinutes(30);
+ }
+
+ Request req = chain.request()
+ .newBuilder()
+ .header("Authorization", "Bearer " + token)
+ .build();
+ return chain.proceed(req);
+ }
+ }
+
/**
* Create an instance of the API for anonymous access.
*/
@@ -66,6 +107,8 @@ public OpenSkyApi() {
* Create an instance of the API for authenticated access
* @param username an OpenSky username
* @param password an OpenSky password for the given username
+ * @deprecated Use OAuth2 clientId/clientSecret authentication flow
+ * @see #OpenSkyApi(OpenSkyAuthentication)
*/
public OpenSkyApi(String username, String password) {
lastRequestTime = new HashMap<>();
@@ -86,6 +129,25 @@ public OpenSkyApi(String username, String password) {
}
}
+ public OpenSkyApi(OpenSkyAuthentication auth) {
+ lastRequestTime = new HashMap<>();
+ // set up JSON mapper
+ mapper = new ObjectMapper();
+ SimpleModule sm = new SimpleModule();
+ sm.addDeserializer(OpenSkyStates.class, new OpenSkyStatesDeserializer());
+ mapper.registerModule(sm);
+
+ authenticated = auth != null;
+
+ if (authenticated) {
+ okHttpClient = new OkHttpClient.Builder()
+ .addInterceptor(new AuthBearerTokenInterceptor(auth))
+ .build();
+ } else {
+ okHttpClient = new OkHttpClient();
+ }
+ }
+
/** Make the actual HTTP Request and return the parsed response
* @param baseUri base uri to request
* @param nvps name value pairs to be sent as query parameters
diff --git a/java/src/main/java/org/opensky/api/OpenSkyAuthentication.java b/java/src/main/java/org/opensky/api/OpenSkyAuthentication.java
new file mode 100644
index 0000000..a0b59f4
--- /dev/null
+++ b/java/src/main/java/org/opensky/api/OpenSkyAuthentication.java
@@ -0,0 +1,63 @@
+package org.opensky.api;
+
+import okhttp3.*;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.JsonNode;
+
+import java.io.IOException;
+
+/**
+ * The Authentication class is responsible for handling authentication
+ * requests to retrieve an access token from the OpenSky Network's authentication API.
+ */
+public class OpenSkyAuthentication {
+ private final String clientId;
+ private final String clientSecret;
+
+ public OpenSkyAuthentication(String clientId, String clientSecret) {
+ this.clientId = clientId;
+ this.clientSecret = clientSecret;
+ }
+
+ /**
+ * The API endpoint for retrieving an access token from the OpenSky Network's authentication system.
+ * This URL is used to send authentication requests with client credentials to obtain access tokens.
+ */
+ private static final String TOKEN_API =
+ "https://auth.opensky-network.org/auth/realms/opensky-network/protocol/openid-connect/token";
+
+
+ public String accessToken() {
+ // Create the OkHttpClient instance
+ OkHttpClient client = new OkHttpClient();
+
+ // Build the request body with the required parameters
+ RequestBody requestBody = new FormBody.Builder()
+ .add("grant_type", "client_credentials")
+ .add("client_id", clientId)
+ .add("client_secret", clientSecret)
+ .build();
+
+ // Create the POST request
+ Request request = new Request.Builder()
+ .url(TOKEN_API)
+ .post(requestBody)
+ .addHeader("Content-Type", "application/x-www-form-urlencoded")
+ .build();
+
+ // Execute the request
+ try (Response response = client.newCall(request).execute()) {
+ if (response.isSuccessful() && response.body() != null) {
+ String responseBody = response.body().string();
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode jsonNode = mapper.readTree(responseBody);
+ return jsonNode.get("access_token").asText();
+ } else {
+ throw new RuntimeException("Failed to fetch access token. Response: " + response);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+}
diff --git a/java/src/main/java/org/opensky/model/OpenSkyStatesDeserializer.java b/java/src/main/java/org/opensky/model/OpenSkyStatesDeserializer.java
index e6145bd..08272f8 100644
--- a/java/src/main/java/org/opensky/model/OpenSkyStatesDeserializer.java
+++ b/java/src/main/java/org/opensky/model/OpenSkyStatesDeserializer.java
@@ -4,6 +4,7 @@
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import java.io.IOException;
@@ -87,16 +88,19 @@ private Collection deserializeStates(JsonParser jp) throws IOExcept
@Override
public OpenSkyStates deserialize(JsonParser jp, DeserializationContext dc) throws IOException {
if (jp.getCurrentToken() != null && jp.getCurrentToken() != JsonToken.START_OBJECT) {
- throw dc.mappingException(OpenSkyStates.class);
+ throw JsonMappingException.from(dc,
+ "Cannot map "
+ + OpenSkyStates.class.getSimpleName()
+ + ".Expected START_OBJECT but got " + jp.getCurrentToken());
}
try {
OpenSkyStates res = new OpenSkyStates();
for (jp.nextToken(); jp.getCurrentToken() != null && jp.getCurrentToken() != JsonToken.END_OBJECT; jp.nextToken()) {
if (jp.getCurrentToken() == JsonToken.FIELD_NAME) {
- if ("time".equalsIgnoreCase(jp.getCurrentName())) {
+ if ("time".equalsIgnoreCase(jp.currentName())) {
int t = jp.nextIntValue(0);
res.setTime(t);
- } else if ("states".equalsIgnoreCase(jp.getCurrentName())) {
+ } else if ("states".equalsIgnoreCase(jp.currentName())) {
jp.nextToken();
res.setStates(deserializeStates(jp));
} else {
@@ -107,7 +111,10 @@ public OpenSkyStates deserialize(JsonParser jp, DeserializationContext dc) throw
}
return res;
} catch (JsonParseException jpe) {
- throw dc.mappingException(OpenSkyStates.class);
+ throw JsonMappingException.from(dc,
+ "Cannot map "
+ + OpenSkyStates.class.getSimpleName()
+ + ".Expected START_OBJECT but got " + jp.getCurrentToken());
}
}
}
diff --git a/java/src/test/java/TestOpenSkyApi.java b/java/src/test/java/TestOpenSkyApi.java
index ef4ae86..11f561f 100644
--- a/java/src/test/java/TestOpenSkyApi.java
+++ b/java/src/test/java/TestOpenSkyApi.java
@@ -1,10 +1,10 @@
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.opensky.api.OpenSkyApi;
import org.opensky.model.OpenSkyStates;
import org.opensky.model.StateVector;
import java.io.IOException;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
/**
* @author Markus Fuchs, fuchs@opensky-network.org
@@ -24,12 +24,12 @@ public void testAnonGetStates() throws IOException, InterruptedException {
OpenSkyStates os = api.getStates(0, null);
long t1 = System.nanoTime();
System.out.println("Request anonStates time = " + ((t1 - t0) / 1000000) + "ms");
- assertTrue("More than 1 state vector", os.getStates().size() > 1);
+ assertTrue(os.getStates().size() > 1, "More than 1 state vector");
int time = os.getTime();
// more than two requests withing ten seconds
os = api.getStates(0, null);
- assertNull("No new data", os);
+ assertNull(os, "No new data");
// wait ten seconds
Thread.sleep(10000);
@@ -40,7 +40,7 @@ public void testAnonGetStates() throws IOException, InterruptedException {
t1 = System.nanoTime();
System.out.println("Request anonStates time = " + ((t1 - t0) / 1000000) + "ms");
assertNotNull(os);
- assertTrue("More than 1 state vector for second valid request", os.getStates().size() > 1);
+ assertTrue(os.getStates().size() > 1, "More than 1 state vector for second valid request");
assertNotEquals(time, os.getTime());
// test bounding box around Switzerland
@@ -75,7 +75,7 @@ public void testAnonGetStates() throws IOException, InterruptedException {
}
OpenSkyStates os2 = api.getStates(0, null, new OpenSkyApi.BoundingBox(45.8389, 47.8229, 5.9962, 10.5226));
- assertTrue("Much less states in Switzerland area than world-wide", os2.getStates().size() < os.getStates().size() - 200);
+ assertTrue(os2.getStates().size() < os.getStates().size() - 200, "Much less states in Switzerland area than world-wide");
}
// can only be tested with a valid account
@@ -88,12 +88,12 @@ public void testAuthGetStates() throws IOException, InterruptedException {
OpenSkyApi api = new OpenSkyApi(USERNAME, PASSWORD);
OpenSkyStates os = api.getStates(0, null);
- assertTrue("More than 1 state vector", os.getStates().size() > 1);
+ assertTrue(os.getStates().size() > 1, "More than 1 state vector");
int time = os.getTime();
// more than two requests withing ten seconds
os = api.getStates(0, null);
- assertNull("No new data", os);
+ assertNull(os, "No new data");
// wait five seconds
Thread.sleep(5000);
@@ -104,7 +104,7 @@ public void testAuthGetStates() throws IOException, InterruptedException {
long t1 = System.nanoTime();
System.out.println("Request authStates time = " + ((t1 - t0) / 1000000) + "ms");
assertNotNull(os);
- assertTrue("More than 1 state vector for second valid request", os.getStates().size() > 1);
+ assertTrue(os.getStates().size() > 1, "More than 1 state vector for second valid request");
assertNotEquals(time, os.getTime());
}
@@ -116,8 +116,8 @@ public void testAnonGetMyStates() {
fail("Anonymous access of 'myStates' expected");
} catch (IllegalAccessError iae) {
// like expected
- assertTrue("Mismatched exception message",
- iae.getMessage().equals("Anonymous access of 'myStates' not allowed"));
+ assertTrue(iae.getMessage().equals("Anonymous access of 'myStates' not allowed"),
+ "Mismatched exception message");
} catch (IOException e) {
fail("Request should not be submitted");
}
@@ -141,7 +141,7 @@ public void testAuthGetMyStates() throws IOException {
OpenSkyApi api = new OpenSkyApi(USERNAME, PASSWORD);
OpenSkyStates os = api.getMyStates(0, null, SERIALS);
- assertTrue("More than 1 state vector", os.getStates().size() > 1);
+ assertTrue(os.getStates().size() > 1, "More than 1 state vector");
for (StateVector sv : os.getStates()) {
// all states contain at least one of the user's sensors
diff --git a/java/src/test/java/TestOpenSkyStatesDeserializer.java b/java/src/test/java/TestOpenSkyStatesDeserializer.java
index d472190..c4400dc 100644
--- a/java/src/test/java/TestOpenSkyStatesDeserializer.java
+++ b/java/src/test/java/TestOpenSkyStatesDeserializer.java
@@ -1,7 +1,7 @@
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import org.opensky.model.OpenSkyStates;
import org.opensky.model.OpenSkyStatesDeserializer;
import org.opensky.model.StateVector;
@@ -9,7 +9,8 @@
import java.io.IOException;
import java.util.Iterator;
-import static org.junit.Assert.*;
+import static java.lang.Double.valueOf;
+import static org.junit.jupiter.api.Assertions.*;
/**
* @author Markus Fuchs, fuchs@opensky-network.org
@@ -33,17 +34,17 @@ public class TestOpenSkyStatesDeserializer {
"[null,\"ABCDEFG\",\"USA\",1001,1000,1.0,2.0,3.0,false,4.0,5.0,6.0,null]" +
"]}";
- @Test(expected = JsonMappingException.class)
+ @Test()
public void testInvalidDeser() throws IOException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule sm = new SimpleModule();
sm.addDeserializer(OpenSkyStates.class, new OpenSkyStatesDeserializer());
mapper.registerModule(sm);
- mapper.readValue(invalidJson, OpenSkyStates.class);
+ assertThrows(JsonMappingException.class, () -> mapper.readValue(invalidJson, OpenSkyStates.class));
}
- @Test(expected = JsonMappingException.class)
+ @Test
public void testInvalidDeser2() throws IOException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule sm = new SimpleModule();
@@ -51,7 +52,7 @@ public void testInvalidDeser2() throws IOException {
mapper.registerModule(sm);
// ObjectMapper throws Exception here
- mapper.readValue("", OpenSkyStates.class);
+ assertThrows(JsonMappingException.class, () -> mapper.readValue("", OpenSkyStates.class));
}
@Test
@@ -77,8 +78,8 @@ public void testDeser() throws IOException {
mapper.registerModule(sm);
OpenSkyStates states = mapper.readValue(validJson, OpenSkyStates.class);
- assertEquals("Correct Time", 1002, states.getTime());
- assertEquals("Number states", 6, states.getStates().size());
+ assertEquals(1002, states.getTime(), "Correct Time");
+ assertEquals(6, states.getStates().size(), "Number states");
// possible cases for state vectors
Iterator statesIt = states.getStates().iterator();
@@ -88,17 +89,17 @@ public void testDeser() throws IOException {
assertEquals( "cabeef", sv.getIcao24());
assertEquals("ABCDEFG", sv.getCallsign());
assertEquals("USA", sv.getOriginCountry());
- assertEquals(new Double(1001), sv.getLastPositionUpdate());
- assertEquals(new Double(1000), sv.getLastContact());
- assertEquals(new Double(1.0), sv.getLongitude());
- assertEquals(new Double(2.0), sv.getLatitude());
- assertEquals(new Double(3.0), sv.getBaroAltitude());
- assertEquals(new Double(4.0), sv.getVelocity());
- assertEquals(new Double(5.0), sv.getHeading());
- assertEquals(new Double(6.0), sv.getVerticalRate());
+ assertEquals(valueOf(1001), sv.getLastPositionUpdate());
+ assertEquals(valueOf(1000), sv.getLastContact());
+ assertEquals(valueOf(1.0), sv.getLongitude());
+ assertEquals(valueOf(2.0), sv.getLatitude());
+ assertEquals(valueOf(3.0), sv.getBaroAltitude());
+ assertEquals(valueOf(4.0), sv.getVelocity());
+ assertEquals(valueOf(5.0), sv.getHeading());
+ assertEquals(valueOf(6.0), sv.getVerticalRate());
assertFalse(sv.isOnGround());
assertNull(sv.getSerials());
- assertEquals(new Double(6743.7), sv.getGeoAltitude());
+ assertEquals(valueOf(6743.7), sv.getGeoAltitude());
assertEquals("6714", sv.getSquawk());
assertFalse(sv.isSpi());
assertEquals(StateVector.PositionSource.ADS_B, sv.getPositionSource());
@@ -109,13 +110,13 @@ public void testDeser() throws IOException {
assertNull(sv.getCallsign());
assertEquals("USA", sv.getOriginCountry());
assertNull(sv.getLastPositionUpdate());
- assertEquals(new Double(1000), sv.getLastContact());
+ assertEquals(valueOf(1000), sv.getLastContact());
assertNull(sv.getLongitude());
assertNull(sv.getLatitude());
assertNull(sv.getGeoAltitude());
- assertEquals(new Double(4.0), sv.getVelocity());
- assertEquals(new Double(5.0), sv.getHeading());
- assertEquals(new Double(6.0), sv.getVerticalRate());
+ assertEquals(valueOf(4.0), sv.getVelocity());
+ assertEquals(valueOf(5.0), sv.getHeading());
+ assertEquals(valueOf(6.0), sv.getVerticalRate());
assertFalse(sv.isOnGround());
assertNull(sv.getSerials());
assertNull(sv.getBaroAltitude());
@@ -128,17 +129,17 @@ public void testDeser() throws IOException {
assertEquals( "cabeef", sv.getIcao24());
assertNull(sv.getCallsign());
assertEquals("USA", sv.getOriginCountry());
- assertEquals(new Double(1001), sv.getLastPositionUpdate());
+ assertEquals(valueOf(1001), sv.getLastPositionUpdate());
assertNull(sv.getLastContact());
- assertEquals(new Double(1.0), sv.getLongitude());
- assertEquals(new Double(2.0), sv.getLatitude());
- assertEquals(new Double(3.0), sv.getBaroAltitude());
+ assertEquals(valueOf(1.0), sv.getLongitude());
+ assertEquals(valueOf(2.0), sv.getLatitude());
+ assertEquals(valueOf(3.0), sv.getBaroAltitude());
assertNull(sv.getVelocity());
assertNull(sv.getHeading());
assertNull(sv.getVerticalRate());
assertFalse(sv.isOnGround());
assertNull(sv.getSerials());
- assertEquals(new Double(6743.7), sv.getGeoAltitude());
+ assertEquals(valueOf(6743.7), sv.getGeoAltitude());
assertNull(sv.getSquawk());
assertFalse(sv.isSpi());
assertEquals(StateVector.PositionSource.ADS_B, sv.getPositionSource());
@@ -148,17 +149,17 @@ public void testDeser() throws IOException {
assertEquals( "cabeef", sv.getIcao24());
assertEquals("ABCDEFG", sv.getCallsign());
assertEquals("USA", sv.getOriginCountry());
- assertEquals(new Double(1001), sv.getLastPositionUpdate());
- assertEquals(new Double(1000), sv.getLastContact());
- assertEquals(new Double(1.0), sv.getLongitude());
- assertEquals(new Double(2.0), sv.getLatitude());
- assertEquals(new Double(3.0), sv.getBaroAltitude());
- assertEquals(new Double(4.0), sv.getVelocity());
- assertEquals(new Double(5.0), sv.getHeading());
- assertEquals(new Double(6.0), sv.getVerticalRate());
+ assertEquals(valueOf(1001), sv.getLastPositionUpdate());
+ assertEquals(valueOf(1000), sv.getLastContact());
+ assertEquals(valueOf(1.0), sv.getLongitude());
+ assertEquals(valueOf(2.0), sv.getLatitude());
+ assertEquals(valueOf(3.0), sv.getBaroAltitude());
+ assertEquals(valueOf(4.0), sv.getVelocity());
+ assertEquals(valueOf(5.0), sv.getHeading());
+ assertEquals(valueOf(6.0), sv.getVerticalRate());
assertFalse(sv.isOnGround());
assertArrayEquals(new Integer[] {1234, 6543}, sv.getSerials().toArray(new Integer[sv.getSerials().size()]));
- assertEquals(new Double(6743.7), sv.getGeoAltitude());
+ assertEquals(valueOf(6743.7), sv.getGeoAltitude());
assertEquals("6714", sv.getSquawk());
assertFalse(sv.isSpi());
assertEquals(StateVector.PositionSource.ASTERIX, sv.getPositionSource());
@@ -168,17 +169,17 @@ public void testDeser() throws IOException {
assertEquals( "cabeef", sv.getIcao24());
assertEquals("ABCDEFG", sv.getCallsign());
assertEquals("USA", sv.getOriginCountry());
- assertEquals(new Double(1001), sv.getLastPositionUpdate());
- assertEquals(new Double(1000), sv.getLastContact());
- assertEquals(new Double(1.0), sv.getLongitude());
- assertEquals(new Double(2.0), sv.getLatitude());
- assertEquals(new Double(3.0), sv.getBaroAltitude());
- assertEquals(new Double(4.0), sv.getVelocity());
- assertEquals(new Double(5.0), sv.getHeading());
- assertEquals(new Double(6.0), sv.getVerticalRate());
+ assertEquals(valueOf(1001), sv.getLastPositionUpdate());
+ assertEquals(valueOf(1000), sv.getLastContact());
+ assertEquals(valueOf(1.0), sv.getLongitude());
+ assertEquals(valueOf(2.0), sv.getLatitude());
+ assertEquals(valueOf(3.0), sv.getBaroAltitude());
+ assertEquals(valueOf(4.0), sv.getVelocity());
+ assertEquals(valueOf(5.0), sv.getHeading());
+ assertEquals(valueOf(6.0), sv.getVerticalRate());
assertFalse(sv.isOnGround());
assertArrayEquals(new Integer[] {1234}, sv.getSerials().toArray(new Integer[sv.getSerials().size()]));
- assertEquals(new Double(6743.7), sv.getGeoAltitude());
+ assertEquals(valueOf(6743.7), sv.getGeoAltitude());
assertEquals("6714", sv.getSquawk());
assertTrue(sv.isSpi());
assertEquals(StateVector.PositionSource.UNKNOWN, sv.getPositionSource());
@@ -189,14 +190,14 @@ public void testDeser() throws IOException {
assertEquals( "cabeef", sv.getIcao24());
assertEquals("ABCDEFG", sv.getCallsign());
assertEquals("USA", sv.getOriginCountry());
- assertEquals(new Double(1001), sv.getLastPositionUpdate());
- assertEquals(new Double(1000), sv.getLastContact());
- assertEquals(new Double(1.0), sv.getLongitude());
- assertEquals(new Double(2.0), sv.getLatitude());
- assertEquals(new Double(3.0), sv.getBaroAltitude());
- assertEquals(new Double(4.0), sv.getVelocity());
- assertEquals(new Double(5.0), sv.getHeading());
- assertEquals(new Double(6.0), sv.getVerticalRate());
+ assertEquals(valueOf(1001), sv.getLastPositionUpdate());
+ assertEquals(valueOf(1000), sv.getLastContact());
+ assertEquals(valueOf(1.0), sv.getLongitude());
+ assertEquals(valueOf(2.0), sv.getLatitude());
+ assertEquals(valueOf(3.0), sv.getBaroAltitude());
+ assertEquals(valueOf(4.0), sv.getVelocity());
+ assertEquals(valueOf(5.0), sv.getHeading());
+ assertEquals(valueOf(6.0), sv.getVerticalRate());
assertTrue(sv.isOnGround());
assertNull(sv.getSerials());
assertNull(sv.getGeoAltitude());