diff --git a/README.md b/README.md index 7e60b0d..85d6355 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ will output something like this: ## Java API -* Maven project (not yet in a public repository) +* Maven project (not yet in a public repository). Clone this repo and build locally (see installation instructions below). * Uses [```OkHttp```](https://square.github.io/okhttp/) for HTTP requests ### Installation @@ -65,12 +65,22 @@ Add the following dependency to your project org.opensky opensky-api - 1.3.0 + 1.4.0 ``` ### Usage +With OAuth clientId / clientSecret: +``` +OpenSkyApi api = new OpenSkyApi("clientId", "clientSecret", true); + OpenSkyStates os = api.getStates(0, null, + new OpenSkyApi.BoundingBox(45.8389, 47.8229, 5.9962, 10.5226)); + + os.getStates().forEach(System.out::println); +``` + +Unauthenticated: ``` OpenSkyStates states = new OpenSkyApi().getStates(0); System.out.println("Number of states: " + states.getStates().size()); @@ -85,7 +95,7 @@ In build.gradle, add the following lines dependencies { /* do not delete the other entries, just add this one */ - compile 'org.opensky:opensky-api:1.3.0' + compile 'org.opensky:opensky-api:1.4.0' } repositories { diff --git a/java/pom.xml b/java/pom.xml index 109265f..abb7c16 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -6,22 +6,23 @@ org.opensky opensky-api - 1.3.0 + 1.4.0 org.apache.maven.plugins maven-compiler-plugin - 3.7.0 + 3.13.0 - 1.7 - 1.7 + 17 + 17 + 17 org.apache.maven.plugins maven-source-plugin - 3.0.1 + 3.3.1 attach-sources @@ -34,7 +35,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.10.4 + 3.10.1 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());