diff --git a/.gitignore b/.gitignore index aeda362..12b4571 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,9 @@ /data/target/ /samples/mqtt/target/ /.license -/management/src/main/proto/ \ No newline at end of file +/management/src/main/proto/ +/samples/account/target/ +/samples/samples-account/target/ +/data/data-common/target/ +/samples/ttnmgmt/target/ +/samples/samples-management/target/ \ No newline at end of file diff --git a/README.md b/README.md index 3ec399b..90f2994 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ This is the Java SDK for [The Things Network](https://www.thethingsnetwork.org) ## Modules -- **[WIP]** [Account](account) - Interact with The Things Network account server -- **[WIP]** [Management](management) - Interact with The Things Network Handler via the API -- **[WIP]** [Data AMQP](data/amqp) - Subscribe to Things Network Handler to send/receive data via AMQP +- [Account](account) - Interact with The Things Network account server +- [Management](management) - Interact with The Things Network Handler via the API +- [Data AMQP](data/amqp) - Subscribe to Things Network Handler to send/receive data via AMQP - [Data MQTT](data/mqtt) - Subscribe to Things Network Handler to send/receive data via MQTT - [Samples](samples) - Samples of how to use previous libraries diff --git a/account/pom.xml b/account/pom.xml index 98c9ee9..beb0913 100644 --- a/account/pom.xml +++ b/account/pom.xml @@ -4,7 +4,7 @@ org.thethingsnetwork app-sdk - 2.0.0 + 2.1.0 account jar @@ -24,17 +24,17 @@ io.reactivex rxjava - 1.1.10 + 1.2.3 com.fasterxml.jackson.core jackson-databind - 2.8.3 + 2.8.5 com.squareup.okhttp3 okhttp - 3.4.2 + 3.5.0 \ No newline at end of file diff --git a/account/src/main/java/org/thethingsnetwork/account/async/AsyncApplication.java b/account/src/main/java/org/thethingsnetwork/account/async/AsyncApplication.java index 2ac6a82..a62ea0b 100644 --- a/account/src/main/java/org/thethingsnetwork/account/async/AsyncApplication.java +++ b/account/src/main/java/org/thethingsnetwork/account/async/AsyncApplication.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,28 +26,38 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import okhttp3.RequestBody; import okhttp3.Response; -import org.thethingsnetwork.account.AbstractApplication; -import org.thethingsnetwork.account.AccessKey; -import org.thethingsnetwork.account.Collaborator; -import org.thethingsnetwork.account.auth.token.OAuth2Token; -import org.thethingsnetwork.account.common.HttpRequest; +import org.thethingsnetwork.account.async.auth.token.AsyncOAuth2Token; +import org.thethingsnetwork.account.common.AbstractApplication; +import org.thethingsnetwork.account.common.AccessKey; +import org.thethingsnetwork.account.common.ApplicationRights; +import org.thethingsnetwork.account.common.Collaborator; +import org.thethingsnetwork.account.sync.Application; +import org.thethingsnetwork.account.util.HttpRequest; import rx.Observable; /** + * This class is an async wrapper for an TTN application. * + * @see Application for a sync version * @author Romain Cambier */ -public class AsyncApplication implements AbstractApplication { +public class AsyncApplication implements AbstractApplication { private String id; private String name; private String created; @JsonIgnore - protected OAuth2Token creds; + protected AsyncOAuth2Token creds; private AsyncApplication() { } + /** + * Create a new application + * + * @param _id the new application ID + * @param _name the new application name + */ public AsyncApplication(String _id, String _name) { id = _id; name = _name; @@ -74,7 +84,7 @@ public String getCreated() { } @Override - public void updateCredentials(OAuth2Token _creds) { + public void updateCredentials(AsyncOAuth2Token _creds) { creds = _creds; } @@ -85,7 +95,13 @@ private void refresh(AsyncApplication _other) { creds = _other.creds; } - public static Observable findAll(OAuth2Token _creds) { + /** + * List all applications available with this token + * + * @param _creds the AsyncOAuth2Token to be used for authentication + * @return the list of AsyncApplication as an Observable stream + */ + public static Observable findAll(AsyncOAuth2Token _creds) { /** * GET /applications */ @@ -98,7 +114,14 @@ public static Observable findAll(OAuth2Token _creds) { .doOnNext((AsyncApplication app) -> app.updateCredentials(_creds)); } - public static Observable create(OAuth2Token _creds, AbstractApplication _app) { + /** + * Create an application + * + * @param _creds the AsyncOAuth2Token to be used for authentication + * @param _app the AsyncApplication template + * @return the new AsyncApplication as an Observable stream. + */ + public static Observable create(AsyncOAuth2Token _creds, AbstractApplication _app) { /** * POST /applications */ @@ -116,7 +139,14 @@ public static Observable create(OAuth2Token _creds, AbstractAp .doOnNext((AsyncApplication ap) -> ap.updateCredentials(_creds)); } - public static Observable findOne(OAuth2Token _creds, String _id) { + /** + * Fetch an application + * + * @param _creds the AsyncOAuth2Token to be used for authentication + * @param _id the application ID to fetch + * @return the AsyncApplication as an Observable stream. + */ + public static Observable findOne(AsyncOAuth2Token _creds, String _id) { /** * GET /applications/{app_id} */ @@ -128,16 +158,34 @@ public static Observable findOne(OAuth2Token _creds, String _i .doOnNext((AsyncApplication app) -> app.updateCredentials(_creds)); } + /** + * Update this application + * + * @return the updated AsyncApplication as an Observable stream. + */ public Observable save() { /** * PATCH /applications/{app_id} */ - /** - * @todo: implement - */ - return Observable.error(new UnsupportedOperationException("Not supported yet.")); + return HttpRequest + .from(creds.getAccountServer() + "/applications/" + id) + .flatMap((HttpRequest t) -> t.inject(creds)) + .flatMap((HttpRequest t) -> HttpRequest + .buildRequestBody(this) + .map((RequestBody rb) -> { + t.getBuilder().patch(rb); + return t; + }) + ) + .flatMap((HttpRequest t) -> t.doExecute()) + .map((i) -> this); } + /** + * Delete this application + * + * @return the updated AsyncApplication as an Observable stream. + */ public Observable delete() { /** * DELETE /applications/{app_id} @@ -150,6 +198,11 @@ public Observable delete() { .map((Response r) -> this); } + /** + * List all EUIs of this application + * + * @return the EUIs of this AsyncApplication as an Observable stream. + */ public Observable findAllEUIs() { /** * GET /applications/{app_id}/euis @@ -162,6 +215,11 @@ public Observable findAllEUIs() { .flatMap(Observable::from); } + /** + * Create a random EUI on this application + * + * @return the new EUI as an Observable stream. + */ public Observable createEUI() { /** * POST /applications/{app_id}/euis @@ -174,7 +232,13 @@ public Observable createEUI() { .map((EuiCreationResponse t) -> t.eui); } - public Observable addEUI(String _eui) { + /** + * Create a defined EUI on this application + * + * @param _eui the new EUI + * @return the updated AsyncApplication as an Observable stream. + */ + public Observable addEUI(String _eui) { /** * PUT /applications/{app_id}/euis/{eui} */ @@ -183,10 +247,16 @@ public Observable addEUI(String _eui) { .flatMap((HttpRequest t) -> t.inject(creds)) .doOnNext((HttpRequest t) -> t.getBuilder().put(RequestBody.create(null, new byte[0]))) .flatMap((HttpRequest t) -> t.doExecute()) - .map((Response r) -> _eui); + .map((Response r) -> this); } - public Observable deleteEUI(String _eui) { + /** + * Delete an EUI from this application + * + * @param _eui the EUI to be deleted + * @return the updated AsyncApplication as an Observable stream. + */ + public Observable deleteEUI(String _eui) { /** * DELETE /applications/{app_id}/euis/{eui} */ @@ -195,9 +265,14 @@ public Observable deleteEUI(String _eui) { .flatMap((HttpRequest t) -> t.inject(creds)) .doOnNext((HttpRequest t) -> t.getBuilder().delete()) .flatMap((HttpRequest t) -> t.doExecute()) - .map((Response r) -> _eui); + .map((Response r) -> this); } + /** + * List all collaborators of this application + * + * @return the list of Collaborator of this AsyncApplication as an Observable stream. + */ public Observable getCollaborators() { /** * GET /applications/{app_id}/collaborators @@ -210,6 +285,12 @@ public Observable getCollaborators() { .flatMap((Collaborator[] cs) -> Observable.from(cs)); } + /** + * Fetch one collaborator from this application + * + * @param _username the username of the Collaborator + * @return the Collaborator as an Observable stream. + */ public Observable findOneCollaborator(String _username) { /** * GET /applications/{app_id}/collaborators/{username} @@ -221,7 +302,13 @@ public Observable findOneCollaborator(String _username) { .flatMap((HttpRequest t) -> t.doExecuteForType(Collaborator.class)); } - public Observable addCollaborator(Collaborator _collaborator) { + /** + * Add a collaborator to this application + * + * @param _collaborator the Collaborator to be added + * @return the updated AsyncApplication as an Observable stream. + */ + public Observable addCollaborator(Collaborator _collaborator) { /** * PUT /applications/{app_id}/collaborators/{username} */ @@ -231,15 +318,21 @@ public Observable addCollaborator(Collaborator _collaborator) { .flatMap((HttpRequest t) -> HttpRequest .buildRequestBody(_collaborator) .map((RequestBody rb) -> { - t.getBuilder().post(rb); + t.getBuilder().put(rb); return t; }) ) .flatMap((HttpRequest t) -> t.doExecute()) - .map((Response c) -> _collaborator); + .map((Response c) -> this); } - public Observable removeCollaborator(Collaborator _collaborator) { + /** + * Remove a collaborator from this application + * + * @param _collaborator the Collaborator to be removed + * @return the updated AsyncApplication as an Observable stream. + */ + public Observable removeCollaborator(Collaborator _collaborator) { /** * DELETE /applications/{app_id}/euis/{eui} */ @@ -248,9 +341,14 @@ public Observable removeCollaborator(Collaborator _collaborator) { .flatMap((HttpRequest t) -> t.inject(creds)) .doOnNext((HttpRequest t) -> t.getBuilder().delete()) .flatMap((HttpRequest t) -> t.doExecute()) - .map((Response c) -> _collaborator); + .map((Response c) -> this); } + /** + * List all access-keys of this application + * + * @return the list of AccessKey of this AsyncApplication as an Observable stream. + */ public Observable getAccessKeys() { /** * GET /applications/{app_id}/access-keys @@ -263,6 +361,12 @@ public Observable getAccessKeys() { .flatMap((AccessKey[] cs) -> Observable.from(cs)); } + /** + * Fetch one access-key of this application + * + * @param _keyname the name of the AccessKey + * @return the AccessKey as an Observable stream. + */ public Observable findOneAccessKey(String _keyname) { /** * GET /applications/{app_id}/access-keys/{keyname} @@ -274,9 +378,15 @@ public Observable findOneAccessKey(String _keyname) { .flatMap((HttpRequest t) -> t.doExecuteForType(AccessKey.class)); } + /** + * Add an access-key to this application + * + * @param _key the AccessKey template + * @return the new AccessKey as an Observable stream. + */ public Observable addAccessKey(AccessKey _key) { /** - * POST /applications/{app_id}/access-keys/{username} + * POST /applications/{app_id}/access-keys */ return HttpRequest .from(creds.getAccountServer() + "/applications/" + getId() + "/access-keys") @@ -288,11 +398,17 @@ public Observable addAccessKey(AccessKey _key) { return t; }) ) - .flatMap((HttpRequest t) -> t.doExecute()) - .map((Response c) -> _key); + .flatMap((HttpRequest t) -> t.doExecuteForType(AccessKey.class)) + .map((AccessKey c) -> c); } - public Observable removeAccessKey(AccessKey _key) { + /** + * Remove an access-key from this application + * + * @param _key the AccessKey + * @return the updated AsyncApplication as an Observable stream. + */ + public Observable removeAccessKey(AccessKey _key) { /** * DELETE /applications/{app_id}/access-keys/{keyname} */ @@ -301,20 +417,36 @@ public Observable removeAccessKey(AccessKey _key) { .flatMap((HttpRequest t) -> t.inject(creds)) .doOnNext((HttpRequest t) -> t.getBuilder().delete()) .flatMap((HttpRequest t) -> t.doExecute()) - .map((Response c) -> _key); + .map((Response c) -> this); } + /** + * Refresh this local application + * + * @return the updated AsyncApplication as an Observable stream. + */ public Observable refresh() { return findOne(creds, getId()) .doOnNext((AsyncApplication t) -> refresh(t)) .map((AsyncApplication app) -> this); } - public Observable getRights() { + /** + * List all rights of this application and token + * + * @return the list of ApplicationRights of this AsyncApplication as an Observable stream. + */ + public Observable getRights() { return getRights(creds); } - public Observable getRights(OAuth2Token _creds) { + /** + * List all rights of the provided token on this application + * + * @param _creds the AsyncOAuth2Token to check right of + * @return the list of ApplicationRights of this AsyncApplication as an Observable stream. + */ + public Observable getRights(AsyncOAuth2Token _creds) { /** * GET /applications/{app_id}/rights */ @@ -322,10 +454,15 @@ public Observable getRights(OAuth2Token _creds) { .from(creds.getAccountServer() + "/applications/" + getId() + "/rights") .flatMap((HttpRequest t) -> t.inject(_creds)) .doOnNext((HttpRequest t) -> t.getBuilder().get()) - .flatMap((HttpRequest t) -> t.doExecuteForType(String[].class)) + .flatMap((HttpRequest t) -> t.doExecuteForType(ApplicationRights[].class)) .flatMap(Observable::from); } + @Override + public String toString() { + return "Application \"" + name + "\" (" + id + ")"; + } + private static class EuiCreationResponse { public String eui; diff --git a/account/src/main/java/org/thethingsnetwork/account/auth/grant/ApplicationPassword.java b/account/src/main/java/org/thethingsnetwork/account/async/auth/grant/AsyncApplicationPassword.java similarity index 67% rename from account/src/main/java/org/thethingsnetwork/account/auth/grant/ApplicationPassword.java rename to account/src/main/java/org/thethingsnetwork/account/async/auth/grant/AsyncApplicationPassword.java index d402f7a..cb60cae 100644 --- a/account/src/main/java/org/thethingsnetwork/account/auth/grant/ApplicationPassword.java +++ b/account/src/main/java/org/thethingsnetwork/account/async/auth/grant/AsyncApplicationPassword.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,22 +21,24 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.thethingsnetwork.account.auth.grant; +package org.thethingsnetwork.account.async.auth.grant; import java.net.URI; import java.util.Base64; import okhttp3.HttpUrl; import okhttp3.RequestBody; -import org.thethingsnetwork.account.auth.token.JsonWebToken; -import org.thethingsnetwork.account.common.HttpRequest; +import org.thethingsnetwork.account.async.auth.token.AsyncJsonWebToken; +import org.thethingsnetwork.account.common.GrantType; +import org.thethingsnetwork.account.util.HttpRequest; import rx.Observable; import rx.Subscriber; /** + * This token provider uses application credentials (id + access-key) to generate a token only usable for the owning application * * @author Romain Cambier */ -public class ApplicationPassword extends GrantType { +public class AsyncApplicationPassword extends GrantType { private final String clientId; private final String clientSecret; @@ -44,7 +46,16 @@ public class ApplicationPassword extends GrantType { private final String key; private final URI accountServer; - public ApplicationPassword(String _appId, String _key, String _clientId, String _clientSecret, URI _accountServer) { + /** + * Create an instance of this token provider using fully-customized settings + * + * @param _appId The application id + * @param _key The application access-key + * @param _clientId The client id you received from the account server + * @param _clientSecret The client secret you received from the account server + * @param _accountServer The account server to be used + */ + public AsyncApplicationPassword(String _appId, String _key, String _clientId, String _clientSecret, URI _accountServer) { if (_key == null) { throw new IllegalArgumentException("key can not be null"); } @@ -67,7 +78,15 @@ public ApplicationPassword(String _appId, String _key, String _clientId, String clientSecret = _clientSecret; } - public ApplicationPassword(String _appId, String _key, String _clientId, String _clientSecret) { + /** + * Create an instance of this token provider using default account server + * + * @param _appId The application id + * @param _key The application access-key + * @param _clientId The client id you received from the account server + * @param _clientSecret The client secret you received from the account server + */ + public AsyncApplicationPassword(String _appId, String _key, String _clientId, String _clientSecret) { this(_appId, _key, _clientId, _clientSecret, GrantType.DEFAULT_ACCOUNT_SERVER); } @@ -80,7 +99,12 @@ private String getBasicAuthHeader() { return "Basic " + Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes()); } - public Observable getToken() { + /** + * Create a token using the settings provided in the constructor + * + * @return the AsyncJsonWebToken as an Observable stream + */ + public Observable getToken() { return Observable .create((Subscriber t) -> { try { @@ -108,13 +132,13 @@ public Observable getToken() { t.getBuilder().header("Authorization", getBasicAuthHeader()); }) .flatMap((HttpRequest t) -> t.doExecuteForType(TokenResponse.class)) - .map((TokenResponse t) -> new JsonWebToken(t.accessToken, System.currentTimeMillis() + 1000 * t.expiresIn, accountServer)); + .map((TokenResponse t) -> new AsyncJsonWebToken(t.accessToken, System.currentTimeMillis() + 1000 * t.expiresIn, accountServer)); } private class TokenRequest { - private final String username = ApplicationPassword.this.appId; - private final String password = ApplicationPassword.this.key; + private final String username = AsyncApplicationPassword.this.appId; + private final String password = AsyncApplicationPassword.this.key; private final String grantType = "password"; } diff --git a/account/src/main/java/org/thethingsnetwork/account/auth/grant/AuthorizationCode.java b/account/src/main/java/org/thethingsnetwork/account/async/auth/grant/AsyncAuthorizationCode.java similarity index 66% rename from account/src/main/java/org/thethingsnetwork/account/auth/grant/AuthorizationCode.java rename to account/src/main/java/org/thethingsnetwork/account/async/auth/grant/AsyncAuthorizationCode.java index 1e4fa0c..305182a 100644 --- a/account/src/main/java/org/thethingsnetwork/account/auth/grant/AuthorizationCode.java +++ b/account/src/main/java/org/thethingsnetwork/account/async/auth/grant/AsyncAuthorizationCode.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,28 +21,37 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.thethingsnetwork.account.auth.grant; +package org.thethingsnetwork.account.async.auth.grant; import java.net.URI; import java.util.Base64; import okhttp3.HttpUrl; import okhttp3.RequestBody; -import org.thethingsnetwork.account.auth.token.RenewableJsonWebToken; -import org.thethingsnetwork.account.common.HttpRequest; +import org.thethingsnetwork.account.async.auth.token.AsyncRenewableJsonWebToken; +import org.thethingsnetwork.account.common.GrantType; +import org.thethingsnetwork.account.util.HttpRequest; import rx.Observable; import rx.Subscriber; /** + * This token provider uses authorization-code flow, meaning it requires user-interraction * * @author Romain Cambier */ -public class AuthorizationCode extends GrantType { +public class AsyncAuthorizationCode extends GrantType { private final String clientId; private final String clientSecret; private final URI accountServer; - public AuthorizationCode(String _clientId, String _clientSecret, URI _accountServer) { + /** + * Create an instance of this token provider using fully-customized settings + * + * @param _clientId The client id you received from the account server + * @param _clientSecret The client secret you received from the account server + * @param _accountServer The account server to be used + */ + public AsyncAuthorizationCode(String _clientId, String _clientSecret, URI _accountServer) { if (_clientId == null) { throw new IllegalArgumentException("clientId can not be null"); } @@ -57,7 +66,13 @@ public AuthorizationCode(String _clientId, String _clientSecret, URI _accountSer accountServer = _accountServer; } - public AuthorizationCode(String _clientId, String _clientSecret) { + /** + * Create an instance of this token provider using default account server + * + * @param _clientId The client id you received from the account server + * @param _clientSecret The client secret you received from the account server + */ + public AsyncAuthorizationCode(String _clientId, String _clientSecret) { this(_clientId, _clientSecret, GrantType.DEFAULT_ACCOUNT_SERVER); } @@ -70,6 +85,16 @@ private String getBasicAuthHeader() { return "Basic " + Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes()); } + /** + * Generate an authorization endpoint to be sent to the user. + * + * Once the user will proceed to authorization (even in case of error) he will redirected to the provided URI, with some query parameters including the code in case of success, or an error if something went wrong + * + * Warning: The redirect URI has to be whitelisted in the account server ! + * + * @param _redirect The redirect URI where the user should be driven after authorization + * @return The HttpUrl where the user should go to access the authorization form + */ public HttpUrl buildAuthorizationURL(URI _redirect) { return new HttpUrl.Builder() .host(accountServer.getHost()) @@ -82,7 +107,13 @@ public HttpUrl buildAuthorizationURL(URI _redirect) { .build(); } - public Observable getToken(String _authorizationCode) { + /** + * Request a token from an authorization-code after successfull authorization + * + * @param _authorizationCode the authorization-code generated by the account server + * @return the AsyncRenewableJsonWebToken as an Observable stream + */ + public Observable getToken(String _authorizationCode) { return Observable .create((Subscriber t) -> { try { @@ -110,10 +141,18 @@ public Observable getToken(String _authorizationCode) { t.getBuilder().header("Authorization", getBasicAuthHeader()); }) .flatMap((HttpRequest t) -> t.doExecuteForType(TokenResponse.class)) - .map((TokenResponse t) -> new RenewableJsonWebToken(t.accessToken, System.currentTimeMillis() + 1000 * t.expiresIn, t.refreshToken, this)); + .map((TokenResponse t) -> new AsyncRenewableJsonWebToken(t.accessToken, System.currentTimeMillis() + 1000 * t.expiresIn, t.refreshToken, this)); } - public Observable refreshToken(RenewableJsonWebToken _token) { + /** + * Request a refresh for the provided token. + * + * This in internally used and should not be used by users + * + * @param _token The Token to be refreshed + * @return the AsyncRenewableJsonWebToken as an Observable stream. The returned one is actually the updated provided one + */ + public Observable refreshToken(AsyncRenewableJsonWebToken _token) { return Observable .create((Subscriber t) -> { try { @@ -146,9 +185,9 @@ public Observable refreshToken(RenewableJsonWebToken _tok private class TokenRequest { - private String clientId = AuthorizationCode.this.clientId; - private String grantType = "authorization_code"; - private String code; + private final String clientId = AsyncAuthorizationCode.this.clientId; + private final String grantType = "authorization_code"; + private final String code; private TokenRequest(String _code) { code = _code; @@ -166,9 +205,9 @@ private static class TokenResponse { private class RefreshRequest { - private String clientId = AuthorizationCode.this.clientId; - private String grantType = "refresh_token"; - private String refresh_token; + private final String clientId = AsyncAuthorizationCode.this.clientId; + private final String grantType = "refresh_token"; + private final String refresh_token; private RefreshRequest(String _refresh_token) { refresh_token = _refresh_token; diff --git a/account/src/main/java/org/thethingsnetwork/account/auth/token/JsonWebToken.java b/account/src/main/java/org/thethingsnetwork/account/async/auth/token/AsyncJsonWebToken.java similarity index 51% rename from account/src/main/java/org/thethingsnetwork/account/auth/token/JsonWebToken.java rename to account/src/main/java/org/thethingsnetwork/account/async/auth/token/AsyncJsonWebToken.java index c96637a..a4c0a23 100644 --- a/account/src/main/java/org/thethingsnetwork/account/auth/token/JsonWebToken.java +++ b/account/src/main/java/org/thethingsnetwork/account/async/auth/token/AsyncJsonWebToken.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,28 +21,30 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.thethingsnetwork.account.auth.token; +package org.thethingsnetwork.account.async.auth.token; import java.net.URI; -import java.util.Arrays; -import java.util.List; -import okhttp3.HttpUrl; -import okhttp3.RequestBody; -import org.thethingsnetwork.account.common.HttpRequest; import rx.Observable; -import rx.Subscriber; /** + * Async Json Web Token wrapper * * @author Romain Cambier */ -public class JsonWebToken implements OAuth2Token { +public class AsyncJsonWebToken implements AsyncOAuth2Token { private String token; private long expiration; private final URI accountServer; - public JsonWebToken(String _token, long _expiration, URI _accountServer) { + /** + * Build an instance + * + * @param _token The raw token + * @param _expiration The token expiration (millis) + * @param _accountServer The account server + */ + public AsyncJsonWebToken(String _token, long _expiration, URI _accountServer) { if (_token == null) { throw new IllegalArgumentException("token can not be null"); } @@ -63,11 +65,16 @@ public boolean hasRefresh() { } @Override - public Observable refresh() { + public Observable refresh() { return Observable.error(new UnsupportedOperationException("Not supported.")); } - protected long getExpiration() { + /** + * Get the expiration + * + * @return The expiration + */ + public long getExpiration() { return expiration; } @@ -81,10 +88,20 @@ public String getToken() { return "Bearer " + token; } + /** + * Set the expiration + * + * @param _expiration The expiration + */ protected void setExpiration(long _expiration) { expiration = _expiration; } + /** + * Set the raw token + * + * @param _token The raw token + */ protected void setToken(String _token) { token = _token; } @@ -94,56 +111,9 @@ public URI getAccountServer() { return accountServer; } - public Observable restrict(List _claims) { - return Observable - .create((Subscriber t) -> { - try { - t.onNext(new HttpUrl.Builder() - .host(accountServer.getHost()) - .scheme(accountServer.getScheme()) - .port(accountServer.getPort() == -1 ? (accountServer.getScheme().equals("http") ? 80 : 443) : accountServer.getPort()) - .addPathSegments("users/restrict-token") - .build() - ); - t.onCompleted(); - } catch (Exception ex) { - t.onError(ex); - } - }) - .flatMap(HttpRequest::from) - .flatMap((HttpRequest t) -> t.inject(this)) - .flatMap((HttpRequest t) -> HttpRequest - .buildRequestBody(new RestrictRequest(_claims)) - .map((RequestBody rb) -> { - t.getBuilder().post(rb); - return t; - }) - ) - .flatMap((HttpRequest t) -> t.doExecuteForType(RestrictResponse.class)) - .map((RestrictResponse t) -> new JsonWebToken(t.accessToken, expiration, accountServer)); - } - - public Observable restrict(String... _claims) { - return restrict(Arrays.asList(_claims)); - } - @Override public String getRawToken() { return token; } - private class RestrictRequest { - - public List scope; - - public RestrictRequest(List _scope) { - scope = _scope; - } - } - - private static class RestrictResponse { - - public String accessToken; - } - } diff --git a/account/src/main/java/org/thethingsnetwork/account/async/auth/token/AsyncOAuth2Token.java b/account/src/main/java/org/thethingsnetwork/account/async/auth/token/AsyncOAuth2Token.java new file mode 100644 index 0000000..824b880 --- /dev/null +++ b/account/src/main/java/org/thethingsnetwork/account/async/auth/token/AsyncOAuth2Token.java @@ -0,0 +1,71 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.account.async.auth.token; + +import java.net.URI; +import rx.Observable; + +/** + * Base interface of any async Oauth2 token + * @author Romain Cambier + */ +public interface AsyncOAuth2Token { + + /** + * Wether or not this token has a refresh method + * @return True if it has + */ + public boolean hasRefresh(); + + /** + * Refresh this token + * @return The refreshed token as an Observable stream + */ + public Observable refresh(); + + /** + * Whether this token has expired + * @return True if this token has expired + */ + public boolean isExpired(); + + /** + * Get the http token + * @return The http token + */ + public String getToken(); + + /** + * Get the raw token + * @return The raw token + */ + public String getRawToken(); + + /** + * Get the account server URI + * @return The account server URI + */ + public URI getAccountServer(); + +} diff --git a/account/src/main/java/org/thethingsnetwork/account/async/auth/token/AsyncRenewableJsonWebToken.java b/account/src/main/java/org/thethingsnetwork/account/async/auth/token/AsyncRenewableJsonWebToken.java new file mode 100644 index 0000000..479dae0 --- /dev/null +++ b/account/src/main/java/org/thethingsnetwork/account/async/auth/token/AsyncRenewableJsonWebToken.java @@ -0,0 +1,174 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.account.async.auth.token; + +import java.util.Arrays; +import java.util.List; +import okhttp3.HttpUrl; +import okhttp3.RequestBody; +import org.thethingsnetwork.account.async.auth.grant.AsyncAuthorizationCode; +import org.thethingsnetwork.account.util.HttpRequest; +import rx.Observable; +import rx.Subscriber; + +/** + * Async Renewable Json Web Token wrapper + * @author Romain Cambier + */ +public class AsyncRenewableJsonWebToken extends AsyncJsonWebToken { + + private String refreshToken; + private final AsyncAuthorizationCode provider; + + /** + * Create an instance + * @param _token The raw token + * @param _expiration The token expiration + * @param _refreshToken The refresh token + * @param _provider The token provider + */ + public AsyncRenewableJsonWebToken(String _token, long _expiration, String _refreshToken, AsyncAuthorizationCode _provider) { + super(_token, _expiration, _provider.getAccountServer()); + if (_refreshToken == null) { + throw new IllegalArgumentException("refreshToken can not be null"); + } + if (_provider == null) { + throw new IllegalArgumentException("provider can not be null"); + } + refreshToken = _refreshToken; + provider = _provider; + } + + @Override + public boolean hasRefresh() { + return true; + } + + /** + * Set the refresh token + * @param _refreshToken The refresh token + */ + protected void setRefreshToken(String _refreshToken) { + refreshToken = _refreshToken; + } + + /** + * Get the refresh token + * @return The refresh token + */ + public String getRefreshToken() { + return refreshToken; + } + + @Override + public Observable refresh() { + return provider.refreshToken(this); + } + + /** + * Restrict this token to a finer claims list + * @param _claims The claims to restrict this token to + * @return A new AsyncRenewableJsonWebToken as an Observable stream + */ + public Observable restrict(List _claims) { + AsyncRenewableJsonWebToken that = this; + return Observable + .create((Subscriber t) -> { + try { + t.onNext(new HttpUrl.Builder() + .host(getAccountServer().getHost()) + .scheme(getAccountServer().getScheme()) + .port(getAccountServer().getPort() == -1 ? (getAccountServer().getScheme().equals("http") ? 80 : 443) : getAccountServer().getPort()) + .addPathSegments("users/restrict-token") + .build() + ); + t.onCompleted(); + } catch (Exception ex) { + t.onError(ex); + } + }) + .flatMap(HttpRequest::from) + .flatMap((HttpRequest t) -> t.inject(this)) + .flatMap((HttpRequest t) -> HttpRequest + .buildRequestBody(new RestrictRequest(_claims)) + .map((RequestBody rb) -> { + t.getBuilder().post(rb); + return t; + }) + ) + .flatMap((HttpRequest t) -> t.doExecuteForType(RestrictResponse.class)) + .map((RestrictResponse t) -> new AsyncRenewableJsonWebToken(t.accessToken, getExpiration(), "", provider) { + + @Override + public Observable refresh() { + return that.refresh() + .map((AsyncOAuth2Token t1) -> (AsyncRenewableJsonWebToken) t1) + .flatMap((AsyncRenewableJsonWebToken t1) -> t1 + .restrict(_claims) + .map((AsyncJsonWebToken t2) -> { + refresh("", t2.getRawToken(), t1.getExpiration()); + return this; + })); + } + + }); + } + + /** + * Restrict this token to a finer claims list + * @param _claims The claims to restrict this token to + * @return A new AsyncRenewableJsonWebToken as an Observable stream + */ + public Observable restrict(String... _claims) { + return restrict(Arrays.asList(_claims)); + } + + /** + * Refresh this token + * @param _refreshToken New refresh token + * @param _accessToken New access token + * @param _expiration New expiration + * @return The updated AsyncRenewableJsonWebToken + */ + public AsyncRenewableJsonWebToken refresh(String _refreshToken, String _accessToken, long _expiration) { + setRefreshToken(_refreshToken); + setToken(_accessToken); + setExpiration(_expiration); + return this; + } + + private class RestrictRequest { + + public List scope; + + public RestrictRequest(List _scope) { + scope = _scope; + } + } + + private static class RestrictResponse { + + public String accessToken; + } +} diff --git a/account/src/main/java/org/thethingsnetwork/account/auth/token/RenewableJsonWebToken.java b/account/src/main/java/org/thethingsnetwork/account/auth/token/RenewableJsonWebToken.java deleted file mode 100644 index c65a50f..0000000 --- a/account/src/main/java/org/thethingsnetwork/account/auth/token/RenewableJsonWebToken.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2016 The Things Network - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.thethingsnetwork.account.auth.token; - -import java.util.Arrays; -import java.util.List; -import org.thethingsnetwork.account.auth.grant.AuthorizationCode; -import rx.Observable; - -/** - * - * @author Romain Cambier - */ -public class RenewableJsonWebToken extends JsonWebToken { - - private String refreshToken; - private final AuthorizationCode provider; - - public RenewableJsonWebToken(String _token, long _expiration, String _refreshToken, AuthorizationCode _provider) { - super(_token, _expiration, _provider.getAccountServer()); - if (_refreshToken == null) { - throw new IllegalArgumentException("refreshToken can not be null"); - } - if (_provider == null) { - throw new IllegalArgumentException("provider can not be null"); - } - refreshToken = _refreshToken; - provider = _provider; - } - - @Override - public boolean hasRefresh() { - return true; - } - - protected void setRefreshToken(String _refreshToken) { - refreshToken = _refreshToken; - } - - public String getRefreshToken() { - return refreshToken; - } - - @Override - public Observable refresh() { - return provider.refreshToken(this); - } - - @Override - public Observable restrict(List _claims) { - RenewableJsonWebToken that = this; - return super.restrict(_claims) - .map((JsonWebToken t) -> new RenewableJsonWebToken(t.getRawToken(), t.getExpiration(), "", provider) { - - @Override - public Observable refresh() { - return that.refresh() - .map((OAuth2Token t1) -> (RenewableJsonWebToken) t1) - .flatMap((RenewableJsonWebToken t1) -> t1 - .restrict(_claims) - .map((JsonWebToken t2) -> { - refresh("", t2.getRawToken(), t1.getExpiration()); - return this; - })); - } - - }); - } - - @Override - public Observable restrict(String... _claims) { - return restrict(Arrays.asList(_claims)); - } - - public RenewableJsonWebToken refresh(String _refreshToken, String _accessToken, long _expiration) { - setRefreshToken(_refreshToken); - setToken(_accessToken); - setExpiration(_expiration); - return this; - } -} diff --git a/account/src/main/java/org/thethingsnetwork/account/common/AbstractApplication.java b/account/src/main/java/org/thethingsnetwork/account/common/AbstractApplication.java new file mode 100644 index 0000000..71e2ca1 --- /dev/null +++ b/account/src/main/java/org/thethingsnetwork/account/common/AbstractApplication.java @@ -0,0 +1,70 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.account.common; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * + * @author Romain Cambier + * @param internal + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public interface AbstractApplication { + + /** + * Get the application ID + * + * @return the application ID + */ + public String getId(); + + /** + * Get the application name + * + * @return the application name + */ + public String getName(); + + /** + * Get the application creation time + * + * @return the application creation time + */ + public String getCreated(); + + /** + * Update the application name + * @param _name the new name to be set + */ + public void setName(String _name); + + /** + * Update the AsyncOAuth2Token to be used by this application wrapper + * @param internal + * @param _creds the new AsyncOAuth2Token to be used + */ + public void updateCredentials(R _creds); + +} diff --git a/account/src/main/java/org/thethingsnetwork/account/AccessKey.java b/account/src/main/java/org/thethingsnetwork/account/common/AccessKey.java similarity index 70% rename from account/src/main/java/org/thethingsnetwork/account/AccessKey.java rename to account/src/main/java/org/thethingsnetwork/account/common/AccessKey.java index e2c6794..e4569e4 100644 --- a/account/src/main/java/org/thethingsnetwork/account/AccessKey.java +++ b/account/src/main/java/org/thethingsnetwork/account/common/AccessKey.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.thethingsnetwork.account; +package org.thethingsnetwork.account.common; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.Collections; @@ -36,26 +36,50 @@ public class AccessKey { private String name; private String key; - private List rights; + private List rights; + /** + * Create an empty AccessKey. Only used by jackson. + */ public AccessKey() { } - public AccessKey(String _name, List _rights) { + /** + * Create a new AccessKey + * + * @param _name The key name + * @param _rights The key rights + */ + public AccessKey(String _name, List _rights) { name = _name; rights = _rights; } + /** + * Get the key name + * + * @return The key name + */ public String getName() { return name; } + /** + * Get the key secret + * + * @return The key secret + */ public String getKey() { return key; } - public List getRights() { + /** + * Get the key rights + * + * @return The key rights + */ + public List getRights() { return Collections.unmodifiableList(rights); } } diff --git a/data/common/src/main/java/org/thethingsnetwork/data/common/Message.java b/account/src/main/java/org/thethingsnetwork/account/common/ApplicationRights.java similarity index 64% rename from data/common/src/main/java/org/thethingsnetwork/data/common/Message.java rename to account/src/main/java/org/thethingsnetwork/account/common/ApplicationRights.java index eea9d36..73ff7b7 100644 --- a/data/common/src/main/java/org/thethingsnetwork/data/common/Message.java +++ b/account/src/main/java/org/thethingsnetwork/account/common/ApplicationRights.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,29 +21,33 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.thethingsnetwork.data.common; +package org.thethingsnetwork.account.common; -import java.util.Base64; -import org.json.JSONException; -import org.json.JSONObject; +import com.fasterxml.jackson.annotation.JsonValue; /** - * This is a wrapper class for JSONObject to provide support for base64 encoded payload * * @author Romain Cambier */ -public class Message extends JSONObject { +public enum ApplicationRights { + + SETTINGS("settings"), + DELETE("delete"), + COLLABORATORS("collaborators"), + MESSAGE_UP_R("messages:up:r"), + MESSAGE_UP_W("messages:up:w"), + MESSAGE_DOWN_W("messages:down:w"), + DEVICES("devices"); - public byte[] getBinary(String _key) { - Object object = get(_key); - if (object instanceof String) { - return Base64.getDecoder().decode((String) object); - } - throw new JSONException("JSONObject[" + quote(_key) + "] is not a base64 decodable string."); + private final String serialized; + + private ApplicationRights(String _serialized) { + serialized = _serialized; } - public Message(String _source) { - super(_source); + @JsonValue + public String toJson() { + return serialized; } } diff --git a/account/src/main/java/org/thethingsnetwork/account/Collaborator.java b/account/src/main/java/org/thethingsnetwork/account/common/Collaborator.java similarity index 76% rename from account/src/main/java/org/thethingsnetwork/account/Collaborator.java rename to account/src/main/java/org/thethingsnetwork/account/common/Collaborator.java index 21b12dc..55ee28c 100644 --- a/account/src/main/java/org/thethingsnetwork/account/Collaborator.java +++ b/account/src/main/java/org/thethingsnetwork/account/common/Collaborator.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.thethingsnetwork.account; +package org.thethingsnetwork.account.common; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.Collections; @@ -38,24 +38,48 @@ public class Collaborator { private String email; private List rights; + /** + * Create an empty Collaborator. Used only by jackson + */ public Collaborator() { } + /** + * Create a new Collaborator + * + * @param _username The username of the Collaborator + * @param _rights The rights of the Collaborator + */ public Collaborator(String _username, List _rights) { username = _username; email = null; rights = _rights; } + /** + * Get the username + * + * @return The username + */ public String getUsername() { return username; } + /** + * Get the email address + * + * @return The email address + */ public String getEmail() { return email; } + /** + * Get the rights + * + * @return The rights + */ public List getRights() { return Collections.unmodifiableList(rights); } diff --git a/account/src/main/java/org/thethingsnetwork/account/auth/grant/GrantType.java b/account/src/main/java/org/thethingsnetwork/account/common/GrantType.java similarity index 88% rename from account/src/main/java/org/thethingsnetwork/account/auth/grant/GrantType.java rename to account/src/main/java/org/thethingsnetwork/account/common/GrantType.java index 62496de..d4fcdd4 100644 --- a/account/src/main/java/org/thethingsnetwork/account/auth/grant/GrantType.java +++ b/account/src/main/java/org/thethingsnetwork/account/common/GrantType.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.thethingsnetwork.account.auth.grant; +package org.thethingsnetwork.account.common; import java.net.URI; import java.net.URISyntaxException; @@ -42,6 +42,10 @@ public abstract class GrantType { } } + /** + * Get the account server used by this token provider + * @return The account server URI + */ public abstract URI getAccountServer(); } diff --git a/account/src/main/java/org/thethingsnetwork/account/sync/Application.java b/account/src/main/java/org/thethingsnetwork/account/sync/Application.java index d84f04c..749ac93 100644 --- a/account/src/main/java/org/thethingsnetwork/account/sync/Application.java +++ b/account/src/main/java/org/thethingsnetwork/account/sync/Application.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,20 +24,28 @@ package org.thethingsnetwork.account.sync; import java.util.List; -import org.thethingsnetwork.account.AbstractApplication; -import org.thethingsnetwork.account.AccessKey; -import org.thethingsnetwork.account.Collaborator; import org.thethingsnetwork.account.async.AsyncApplication; -import org.thethingsnetwork.account.auth.token.OAuth2Token; +import org.thethingsnetwork.account.common.AbstractApplication; +import org.thethingsnetwork.account.common.AccessKey; +import org.thethingsnetwork.account.common.ApplicationRights; +import org.thethingsnetwork.account.common.Collaborator; +import org.thethingsnetwork.account.sync.auth.token.OAuth2Token; /** + * This class is a wrapper for an TTN application. * * @author Romain Cambier */ -public class Application implements AbstractApplication { +public class Application implements AbstractApplication { private final AsyncApplication wrapped; + /** + * Create a new application + * + * @param _id the new application ID + * @param _name the new application name + */ public Application(String _id, String _name) { wrapped = new AsyncApplication(_id, _name); } @@ -46,28 +54,53 @@ private Application(AsyncApplication _wrap) { wrapped = _wrap; } + /** + * List all applications available with this token + * + * @param _creds the OAuth2Token to be used for authentication + * @return the list of Application + */ public static List findAll(OAuth2Token _creds) { - return AsyncApplication.findAll(_creds) + return AsyncApplication.findAll(_creds.async()) .map((AsyncApplication t) -> new Application(t)) .toList() .toBlocking() .single(); } + /** + * Create an application + * + * @param _creds the OAuth2Token to be used for authentication + * @param _app the Application template + * @return the new Application + */ public static Application create(OAuth2Token _creds, AbstractApplication _app) { - return AsyncApplication.create(_creds, _app) + return AsyncApplication.create(_creds.async(), _app) .map((AsyncApplication t) -> new Application(t)) .toBlocking() .single(); } + /** + * Fetch an application + * + * @param _creds the OAuth2Token to be used for authentication + * @param _id the application ID to fetch + * @return the Application + */ public static Application findOne(OAuth2Token _creds, String _id) { - return AsyncApplication.findOne(_creds, _id) + return AsyncApplication.findOne(_creds.async(), _id) .map((AsyncApplication t) -> new Application(t)) .toBlocking() .singleOrDefault(null); } + /** + * Update this application + * + * @return the updated Application + */ public Application save() { return wrapped.save() .map((AsyncApplication t) -> this) @@ -75,6 +108,11 @@ public Application save() { .single(); } + /** + * Delete this application + * + * @return the updated Application + */ public Application delete() { return wrapped.delete() .map((AsyncApplication t) -> this) @@ -82,6 +120,11 @@ public Application delete() { .single(); } + /** + * List all EUIs of this application + * + * @return the EUIs of this Application + */ public List findAllEUIs() { return wrapped.findAllEUIs() .toList() @@ -89,24 +132,48 @@ public List findAllEUIs() { .single(); } + /** + * Create a random EUI on this application + * + * @return the new EUI + */ public String createEUI() { return wrapped.createEUI() .toBlocking() .single(); } - public String addEUI(String _eui) { + /** + * Create a defined EUI on this application + * + * @param _eui the new EUI + * @return the updated Application + */ + public Application addEUI(String _eui) { return wrapped.addEUI(_eui) + .map((i) -> this) .toBlocking() .single(); } - public String deleteEUI(String _eui) { + /** + * Delete an EUI from this application + * + * @param _eui the EUI to be deleted + * @return the updated Application + */ + public Application deleteEUI(String _eui) { return wrapped.deleteEUI(_eui) + .map((i) -> this) .toBlocking() .single(); } + /** + * List all collaborators of this application + * + * @return the list of Collaborator of this Application + */ public List getCollaborators() { return wrapped.getCollaborators() .toList() @@ -114,24 +181,49 @@ public List getCollaborators() { .single(); } + /** + * Fetch one collaborator from this application + * + * @param _username the username of the Collaborator + * @return the Collaborator + */ public Collaborator findOneCollaborator(String _username) { return wrapped.findOneCollaborator(_username) .toBlocking() .singleOrDefault(null); } - public Collaborator addCollaborator(Collaborator _collaborator) { + /** + * Add a collaborator to this application + * + * @param _collaborator the Collaborator to be added + * @return the updated Application + */ + public Application addCollaborator(Collaborator _collaborator) { return wrapped.addCollaborator(_collaborator) + .map((i) -> this) .toBlocking() .single(); } - public Collaborator removeCollaborator(Collaborator _collaborator) { + /** + * Remove a collaborator from this application + * + * @param _collaborator the Collaborator to be removed + * @return the updated Application + */ + public Application removeCollaborator(Collaborator _collaborator) { return wrapped.removeCollaborator(_collaborator) + .map((i) -> this) .toBlocking() .single(); } + /** + * List all access-keys of this application + * + * @return the list of AccessKey of this Application + */ public List getAccessKeys() { return wrapped.getAccessKeys() .toList() @@ -139,24 +231,48 @@ public List getAccessKeys() { .single(); } + /** + * Fetch one access-key of this application + * + * @param _keyname the name of the AccessKey + * @return the AccessKey + */ public AccessKey findOneAccessKey(String _keyname) { return wrapped.findOneAccessKey(_keyname) .toBlocking() .singleOrDefault(null); } + /** + * Add an access-key to this application + * + * @param _key the AccessKey template + * @return the new AccessKey + */ public AccessKey addAccessKey(AccessKey _key) { return wrapped.addAccessKey(_key) .toBlocking() .single(); } - public AccessKey removeAccessKey(AccessKey _key) { + /** + * Remove an access-key from this application + * + * @param _key the AccessKey + * @return the updated Application + */ + public Application removeAccessKey(AccessKey _key) { return wrapped.removeAccessKey(_key) + .map((i) -> this) .toBlocking() .single(); } + /** + * Refresh this local application + * + * @return the updated Application + */ public Application refresh() { return wrapped.refresh() .map((AsyncApplication app) -> this) @@ -164,15 +280,26 @@ public Application refresh() { .single(); } - public List getRights() { + /** + * List all rights of this application and token + * + * @return the list of ApplicationRights of this Application + */ + public List getRights() { return wrapped.getRights() .toList() .toBlocking() .single(); } - public List getRights(OAuth2Token _creds) { - return wrapped.getRights(_creds) + /** + * List all rights of the provided token on this application + * + * @param _creds the OAuth2Token to check right of + * @return the list of ApplicationRights of this Application + */ + public List getRights(OAuth2Token _creds) { + return wrapped.getRights(_creds.async()) .toList() .toBlocking() .single(); @@ -200,7 +327,7 @@ public void setName(String _name) { @Override public void updateCredentials(OAuth2Token _creds) { - wrapped.updateCredentials(_creds); + wrapped.updateCredentials(_creds.async()); } } diff --git a/account/src/main/java/org/thethingsnetwork/account/sync/auth/grant/ApplicationPassword.java b/account/src/main/java/org/thethingsnetwork/account/sync/auth/grant/ApplicationPassword.java new file mode 100644 index 0000000..d480d33 --- /dev/null +++ b/account/src/main/java/org/thethingsnetwork/account/sync/auth/grant/ApplicationPassword.java @@ -0,0 +1,83 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.account.sync.auth.grant; + +import java.net.URI; +import org.thethingsnetwork.account.async.auth.grant.AsyncApplicationPassword; +import org.thethingsnetwork.account.async.auth.token.AsyncJsonWebToken; +import org.thethingsnetwork.account.common.GrantType; +import org.thethingsnetwork.account.sync.auth.token.JsonWebToken; + +/** + * This token provider uses application credentials (id + access-key) to generate a token only usable for the owning application + * + * @author Romain Cambier + */ +public class ApplicationPassword extends GrantType { + + private final AsyncApplicationPassword wrapped; + + /** + * Create an instance of this token provider using fully-customized settings + * + * @param _appId The application id + * @param _key The application access-key + * @param _clientId The client id you received from the account server + * @param _clientSecret The client secret you received from the account server + * @param _accountServer The account server to be used + */ + public ApplicationPassword(String _appId, String _key, String _clientId, String _clientSecret, URI _accountServer) { + wrapped = new AsyncApplicationPassword(_appId, _key, _clientId, _clientSecret, _accountServer); + } + + /** + * Create an instance of this token provider using default account server + * + * @param _appId The application id + * @param _key The application access-key + * @param _clientId The client id you received from the account server + * @param _clientSecret The client secret you received from the account server + */ + public ApplicationPassword(String _appId, String _key, String _clientId, String _clientSecret) { + wrapped = new AsyncApplicationPassword(_appId, _key, _clientId, _clientSecret); + } + + @Override + public URI getAccountServer() { + return wrapped.getAccountServer(); + } + + /** + * Create a token using the settings provided in the constructor + * + * @return the JsonWebToken as an Observable stream + */ + public JsonWebToken getToken() { + return wrapped.getToken() + .map((AsyncJsonWebToken t) -> new JsonWebToken(t)) + .toBlocking() + .single(); + } + +} diff --git a/account/src/main/java/org/thethingsnetwork/account/sync/auth/grant/AuthorizationCode.java b/account/src/main/java/org/thethingsnetwork/account/sync/auth/grant/AuthorizationCode.java new file mode 100644 index 0000000..7d97afc --- /dev/null +++ b/account/src/main/java/org/thethingsnetwork/account/sync/auth/grant/AuthorizationCode.java @@ -0,0 +1,94 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.account.sync.auth.grant; + +import java.net.URI; +import okhttp3.HttpUrl; +import org.thethingsnetwork.account.async.auth.grant.AsyncAuthorizationCode; +import org.thethingsnetwork.account.async.auth.token.AsyncRenewableJsonWebToken; +import org.thethingsnetwork.account.common.GrantType; +import org.thethingsnetwork.account.sync.auth.token.RenewableJsonWebToken; + +/** + * This token provider uses authorization-code flow, meaning it requires user-interraction + * @author Romain Cambier + */ +public class AuthorizationCode extends GrantType{ + + private final AsyncAuthorizationCode wrapped; + + /** + * Create an instance of this token provider using fully-customized settings + * + * @param _clientId The client id you received from the account server + * @param _clientSecret The client secret you received from the account server + * @param _accountServer The account server to be used + */ + public AuthorizationCode(String _clientId, String _clientSecret, URI _accountServer) { + wrapped = new AsyncAuthorizationCode(_clientId, _clientSecret, _accountServer); + } + + /** + * Create an instance of this token provider using default account server + * + * @param _clientId The client id you received from the account server + * @param _clientSecret The client secret you received from the account server + */ + public AuthorizationCode(String _clientId, String _clientSecret) { + wrapped = new AsyncAuthorizationCode(_clientId, _clientSecret); + } + + @Override + public URI getAccountServer() { + return wrapped.getAccountServer(); + } + + /** + * Generate an authorization endpoint to be sent to the user. + * + * Once the user will proceed to authorization (even in case of error) he will redirected to the provided URI, with some query parameters including the code in case of success, or an error if something went wrong + * + * Warning: The redirect URI has to be whitelisted in the account server ! + * + * @param _redirect The redirect URI where the user should be driven after authorization + * @return The HttpUrl where the user should go to access the authorization form + */ + public HttpUrl buildAuthorizationURL(URI _redirect) { + return wrapped.buildAuthorizationURL(_redirect); + } + + /** + * Request a token from an authorization-code after successfull authorization + * + * @param _authorizationCode the authorization-code generated by the account server + * @return the RenewableJsonWebToken + */ + public RenewableJsonWebToken getToken(String _authorizationCode) { + return wrapped.getToken(_authorizationCode) + .map((AsyncRenewableJsonWebToken t) -> new RenewableJsonWebToken(t)) + .toBlocking() + .single(); + } + +} diff --git a/account/src/main/java/org/thethingsnetwork/account/sync/auth/token/JsonWebToken.java b/account/src/main/java/org/thethingsnetwork/account/sync/auth/token/JsonWebToken.java new file mode 100644 index 0000000..8f200d9 --- /dev/null +++ b/account/src/main/java/org/thethingsnetwork/account/sync/auth/token/JsonWebToken.java @@ -0,0 +1,85 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.account.sync.auth.token; + +import java.net.URI; +import org.thethingsnetwork.account.async.auth.token.AsyncJsonWebToken; + +/** + * Json Web Token wrapper + * @author Romain Cambier + */ +public class JsonWebToken implements OAuth2Token { + + private final AsyncJsonWebToken wrapped; + + public JsonWebToken(AsyncJsonWebToken _wrap) { + wrapped = _wrap; + } + + @Override + public boolean hasRefresh() { + return wrapped.hasRefresh(); + } + + /** + * Get the expiration + * + * @return The expiration + */ + protected long getExpiration() { + return wrapped.getExpiration(); + } + + @Override + public JsonWebToken refresh() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public boolean isExpired() { + return wrapped.isExpired(); + } + + @Override + public String getToken() { + return wrapped.getToken(); + } + + @Override + public String getRawToken() { + return wrapped.getRawToken(); + } + + @Override + public URI getAccountServer() { + return wrapped.getAccountServer(); + } + + @Override + public AsyncJsonWebToken async() { + return wrapped; + } + +} diff --git a/account/src/main/java/org/thethingsnetwork/account/sync/auth/token/OAuth2Token.java b/account/src/main/java/org/thethingsnetwork/account/sync/auth/token/OAuth2Token.java new file mode 100644 index 0000000..2966b75 --- /dev/null +++ b/account/src/main/java/org/thethingsnetwork/account/sync/auth/token/OAuth2Token.java @@ -0,0 +1,77 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.account.sync.auth.token; + +import java.net.URI; +import org.thethingsnetwork.account.async.auth.token.AsyncOAuth2Token; + +/** + * + * @author Romain Cambier + */ +public interface OAuth2Token { + + /** + * Wether or not this token has a refresh method + * @return True if it has + */ + public boolean hasRefresh(); + + /** + * Refresh this token + * @return The refreshed token + */ + public OAuth2Token refresh(); + + /** + * Whether this token has expired + * @return True if this token has expired + */ + public boolean isExpired(); + + /** + * Get the http token + * @return The http token + */ + public String getToken(); + + /** + * Get the raw token + * @return The raw token + */ + public String getRawToken(); + + /** + * Get the account server URI + * @return The account server URI + */ + public URI getAccountServer(); + + /** + * Get the wrapped async token + * @return The wrapped async token + */ + public AsyncOAuth2Token async(); + +} diff --git a/account/src/main/java/org/thethingsnetwork/account/sync/auth/token/RenewableJsonWebToken.java b/account/src/main/java/org/thethingsnetwork/account/sync/auth/token/RenewableJsonWebToken.java new file mode 100644 index 0000000..a93a961 --- /dev/null +++ b/account/src/main/java/org/thethingsnetwork/account/sync/auth/token/RenewableJsonWebToken.java @@ -0,0 +1,106 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.account.sync.auth.token; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; +import org.thethingsnetwork.account.async.auth.token.AsyncRenewableJsonWebToken; + +/** + * Renewable Json Web Token wrapper + * + * @author Romain Cambier + */ +public class RenewableJsonWebToken implements OAuth2Token { + + private final AsyncRenewableJsonWebToken wrapped; + + public RenewableJsonWebToken(AsyncRenewableJsonWebToken _wrap) { + wrapped = _wrap; + } + + @Override + public boolean hasRefresh() { + return wrapped.hasRefresh(); + } + + @Override + public RenewableJsonWebToken refresh() { + return wrapped + .refresh() + .map((AsyncRenewableJsonWebToken t) -> this) + .toBlocking() + .single(); + } + + @Override + public boolean isExpired() { + return wrapped.isExpired(); + } + + @Override + public String getToken() { + return wrapped.getToken(); + } + + @Override + public String getRawToken() { + return wrapped.getRawToken(); + } + + @Override + public URI getAccountServer() { + return wrapped.getAccountServer(); + } + + /** + * Restrict this token to a finer claims list + * + * @param _claims The claims to restrict this token to + * @return A new RenewableJsonWebToken + */ + public RenewableJsonWebToken restrict(List _claims) { + return wrapped.restrict(_claims) + .map((AsyncRenewableJsonWebToken t) -> new RenewableJsonWebToken(t)) + .toBlocking() + .single(); + } + + /** + * Restrict this token to a finer claims list + * + * @param _claims The claims to restrict this token to + * @return A new RenewableJsonWebToken + */ + public RenewableJsonWebToken restrict(String... _claims) { + return restrict(Arrays.asList(_claims)); + } + + @Override + public AsyncRenewableJsonWebToken async() { + return wrapped; + } + +} diff --git a/account/src/main/java/org/thethingsnetwork/account/common/HttpException.java b/account/src/main/java/org/thethingsnetwork/account/util/HttpException.java similarity index 94% rename from account/src/main/java/org/thethingsnetwork/account/common/HttpException.java rename to account/src/main/java/org/thethingsnetwork/account/util/HttpException.java index 68f40b6..9416403 100644 --- a/account/src/main/java/org/thethingsnetwork/account/common/HttpException.java +++ b/account/src/main/java/org/thethingsnetwork/account/util/HttpException.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.thethingsnetwork.account.common; +package org.thethingsnetwork.account.util; /** * diff --git a/account/src/main/java/org/thethingsnetwork/account/common/HttpRequest.java b/account/src/main/java/org/thethingsnetwork/account/util/HttpRequest.java similarity index 87% rename from account/src/main/java/org/thethingsnetwork/account/common/HttpRequest.java rename to account/src/main/java/org/thethingsnetwork/account/util/HttpRequest.java index 92dbff8..25945e8 100644 --- a/account/src/main/java/org/thethingsnetwork/account/common/HttpRequest.java +++ b/account/src/main/java/org/thethingsnetwork/account/util/HttpRequest.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.thethingsnetwork.account.common; +package org.thethingsnetwork.account.util; import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; import com.fasterxml.jackson.annotation.PropertyAccessor; @@ -37,7 +37,7 @@ import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; -import org.thethingsnetwork.account.auth.token.OAuth2Token; +import org.thethingsnetwork.account.async.auth.token.AsyncOAuth2Token; import rx.Observable; import rx.Subscriber; import rx.schedulers.Schedulers; @@ -100,13 +100,13 @@ public static Observable from(HttpUrl _url) { }); } - public Observable inject(OAuth2Token _creds) { + public Observable inject(AsyncOAuth2Token _creds) { if (_creds == null) { return Observable.just(this); } if (_creds.isExpired()) { if (_creds.hasRefresh()) { - return _creds.refresh().flatMap((OAuth2Token t) -> inject(t)); + return _creds.refresh().flatMap((AsyncOAuth2Token t) -> inject(t)); } else { return Observable.error(new Exception("non-renewable token expired")); } @@ -154,6 +154,21 @@ public Observable doExecute() { client.newCall(r).enqueue(new SubscriberCallback(t)); }) .subscribeOn(Schedulers.io()) + ) + .flatMap((Response r) -> Observable + .create((Subscriber t) -> { + try { + if (!r.isSuccessful()) { + t.onError(new HttpException(r.code(), r.message(), new String(r.body().bytes()))); + return; + } + t.onNext(r); + t.onCompleted(); + } catch (IOException ex) { + t.onError(ex); + } + }) + .subscribeOn(Schedulers.io()) ); } @@ -196,6 +211,10 @@ public Observable doExecuteForType(Class _type) { } + public static void shutdown() { + client.dispatcher().executorService().shutdown(); + } + public Request.Builder getBuilder() { return builder; } diff --git a/data/common/src/main/java/org/thethingsnetwork/data/common/TriConsumer.java b/data/common/src/main/java/org/thethingsnetwork/data/common/TriConsumer.java deleted file mode 100644 index e9dde5b..0000000 --- a/data/common/src/main/java/org/thethingsnetwork/data/common/TriConsumer.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Shareif.com CONFIDENTIAL - * ________________________ - * - * Copyright 2016 Shareif.com SPRL - * All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Shareif.com SPRL and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to Shareif.com SPRL - * and its suppliers and may be covered by Belgian and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from Shareif.com SPRL. - */ -package org.thethingsnetwork.data.common; - -/** - * - * @author Romain Cambier - */ -@FunctionalInterface -public interface TriConsumer { - - public void accept(T t, U u, V v); -} diff --git a/data/amqp/pom.xml b/data/data-amqp/pom.xml similarity index 97% rename from data/amqp/pom.xml rename to data/data-amqp/pom.xml index 1bf65cf..39f0a3b 100644 --- a/data/amqp/pom.xml +++ b/data/data-amqp/pom.xml @@ -4,7 +4,7 @@ org.thethingsnetwork data - 2.0.0 + 2.1.0 data-amqp jar diff --git a/data/amqp/src/main/java/org/thethingsnetwork/data/amqp/Client.java b/data/data-amqp/src/main/java/org/thethingsnetwork/data/amqp/Client.java similarity index 74% rename from data/amqp/src/main/java/org/thethingsnetwork/data/amqp/Client.java rename to data/data-amqp/src/main/java/org/thethingsnetwork/data/amqp/Client.java index c401ac2..77a52be 100644 --- a/data/amqp/src/main/java/org/thethingsnetwork/data/amqp/Client.java +++ b/data/data-amqp/src/main/java/org/thethingsnetwork/data/amqp/Client.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -31,8 +31,6 @@ import com.rabbitmq.client.Envelope; import java.io.IOException; import java.net.URISyntaxException; -import java.nio.ByteBuffer; -import java.util.Base64; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -43,8 +41,8 @@ import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Consumer; -import org.json.JSONObject; import org.thethingsnetwork.data.common.AbstractClient; +import static org.thethingsnetwork.data.common.AbstractClient.MAPPER; import org.thethingsnetwork.data.common.Subscribable; import org.thethingsnetwork.data.common.TriConsumer; import org.thethingsnetwork.data.common.events.AbstractEventHandler; @@ -53,12 +51,17 @@ import org.thethingsnetwork.data.common.events.ErrorHandler; import org.thethingsnetwork.data.common.events.EventHandler; import org.thethingsnetwork.data.common.events.UplinkHandler; +import org.thethingsnetwork.data.common.messages.ActivationMessage; +import org.thethingsnetwork.data.common.messages.DataMessage; +import org.thethingsnetwork.data.common.messages.DownlinkMessage; +import org.thethingsnetwork.data.common.messages.RawMessage; +import org.thethingsnetwork.data.common.messages.UplinkMessage; /** * * @author Romain Cambier */ -public class Client implements AbstractClient { +public class Client extends AbstractClient { /** * Connection settings @@ -129,36 +132,55 @@ public Client start() throws Exception { connection = factory.newConnection(); channel = connection.createChannel(); - final String queue = channel.queueDeclare().getQueue(); + String queue = channel.queueDeclare().getQueue(); channel.basicConsume(queue, new DefaultConsumer(channel) { @Override - public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, final byte[] body) throws IOException { - final String[] tokens = envelope.getRoutingKey().split("\\."); + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + String[] tokens = envelope.getRoutingKey().split("\\."); if (tokens.length < 4) { return; } switch (tokens[3]) { case "up": if (handlers.containsKey(UplinkHandler.class)) { - handlers.get(UplinkHandler.class).stream().forEach((handler) -> { - executor.submit(() -> { - try { - UplinkHandler uh = (UplinkHandler) handler; - if (uh.matches(tokens[2])) { - uh.handle(tokens[2], uh.transform(new String(body))); - } - } catch (final Exception ex) { - if (handlers.containsKey(ErrorHandler.class)) { - handlers.get(ErrorHandler.class).stream().forEach((org.thethingsnetwork.data.common.events.EventHandler handler1) -> { - executor.submit(() -> { - ((ErrorHandler) handler1).safelyHandle(ex); - }); - }); - } - } - }); - }); + String field; + if (tokens.length > 4) { + field = concat(4, tokens); + } else { + field = null; + } + handlers.get(UplinkHandler.class).stream() + .forEach((handler) -> { + executor.submit(() -> { + try { + UplinkHandler uh = (UplinkHandler) handler; + + if (uh.matches(tokens[2], field)) { + if (uh.isField()) { + uh.handle(tokens[2], new RawMessage() { + String str = new String(body); + + @Override + public String asString() { + return str; + } + }); + } else { + uh.handle(tokens[2], MAPPER.readValue(body, UplinkMessage.class)); + } + } + } catch (Exception ex) { + if (handlers.containsKey(ErrorHandler.class)) { + handlers.get(ErrorHandler.class).stream().forEach((org.thethingsnetwork.data.common.events.EventHandler handler1) -> { + executor.submit(() -> { + ((ErrorHandler) handler1).safelyHandle(ex); + }); + }); + } + } + }); + }); } break; case "events": @@ -171,9 +193,9 @@ public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProp try { ActivationHandler ah = (ActivationHandler) handler; if (ah.matches(tokens[2])) { - ah.handle(tokens[2], new JSONObject(new String(body))); + ah.handle(tokens[2], MAPPER.readValue(body, ActivationMessage.class)); } - } catch (final Exception ex) { + } catch (Exception ex) { if (handlers.containsKey(ErrorHandler.class)) { handlers.get(ErrorHandler.class).stream().forEach((org.thethingsnetwork.data.common.events.EventHandler handler1) -> { executor.submit(() -> { @@ -194,9 +216,16 @@ public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProp AbstractEventHandler aeh = (AbstractEventHandler) handler; String event = concat(4, tokens); if (aeh.matches(tokens[2], event)) { - aeh.handle(tokens[2], event, new JSONObject(new String(body))); + aeh.handle(tokens[2], event, new RawMessage() { + String str = new String(body); + + @Override + public String asString() { + return str; + } + }); } - } catch (final Exception ex) { + } catch (Exception ex) { if (handlers.containsKey(ErrorHandler.class)) { handlers.get(ErrorHandler.class).stream().forEach((org.thethingsnetwork.data.common.events.EventHandler handler1) -> { executor.submit(() -> { @@ -249,7 +278,7 @@ public String getPathWildcard() { executor.submit(() -> { try { ((ConnectHandler) handler).handle(() -> channel); - } catch (final Exception ex) { + } catch (Exception ex) { if (handlers.containsKey(ErrorHandler.class)) { handlers.get(ErrorHandler.class).stream().forEach((org.thethingsnetwork.data.common.events.EventHandler handler1) -> { executor.submit(() -> { @@ -304,34 +333,12 @@ public Client endNow() throws IOException { } @Override - public void send(String _devId, byte[] _payload, int _port) throws IOException { - JSONObject data = new JSONObject(); - data.put("payload_raw", Base64.getEncoder().encodeToString(_payload)); - data.put("port", _port != 0 ? _port : 1); - channel.basicPublish(exchange, appId + "/devices/" + _devId + "/down", null, data.toString().getBytes()); - } - - @Override - public void send(String _devId, JSONObject _payload, int _port) throws IOException { - JSONObject data = new JSONObject(); - data.put("payload_fields", _payload); - data.put("port", _port != 0 ? _port : 1); - channel.basicPublish(exchange, appId + "/devices/" + _devId + "/down", null, data.toString().getBytes()); - } - - @Override - public void send(String _devId, ByteBuffer _payload, int _port) throws IOException { - JSONObject data = new JSONObject(); - _payload.rewind(); - byte[] payload = new byte[_payload.capacity() - _payload.remaining()]; - _payload.get(payload); - data.put("payload_fields", Base64.getEncoder().encodeToString(payload)); - data.put("port", _port != 0 ? _port : 1); - channel.basicPublish(exchange, appId + "/devices/" + _devId + "/down", null, data.toString().getBytes()); + public void send(String _devId, DownlinkMessage _payload) throws IOException { + channel.basicPublish(exchange, appId + "/devices/" + _devId + "/down", null, MAPPER.writeValueAsBytes(_payload)); } @Override - public Client onConnected(final Consumer _handler) { + public Client onConnected(Consumer _handler) { if (connection != null) { throw new RuntimeException("Already connected"); } @@ -349,7 +356,7 @@ public void handle(org.thethingsnetwork.data.common.Connection _client) { } @Override - public Client onError(final Consumer _handler) { + public Client onError(Consumer _handler) { if (connection != null) { throw new RuntimeException("Already connected"); } @@ -366,7 +373,7 @@ public void handle(Throwable _error) { } @Override - public Client onMessage(final String _devId, final String _field, final BiConsumer _handler) { + public Client onMessage(String _devId, String _field, BiConsumer _handler) { if (connection != null) { throw new RuntimeException("Already connected"); } @@ -375,7 +382,7 @@ public Client onMessage(final String _devId, final String _field, final BiConsum } handlers.get(UplinkHandler.class).add(new UplinkHandler() { @Override - public void handle(String _devId, Object _data) { + public void handle(String _devId, DataMessage _data) { _handler.accept(_devId, _data); } @@ -393,17 +400,17 @@ public String getField() { } @Override - public Client onMessage(final String _devId, final BiConsumer _handler) { + public Client onMessage(String _devId, BiConsumer _handler) { return onMessage(_devId, null, _handler); } @Override - public Client onMessage(final BiConsumer _handler) { + public Client onMessage(BiConsumer _handler) { return onMessage(null, null, _handler); } @Override - public Client onActivation(final String _devId, final BiConsumer _handler) { + public Client onActivation(String _devId, BiConsumer _handler) { if (connection != null) { throw new RuntimeException("Already connected"); } @@ -412,7 +419,7 @@ public Client onActivation(final String _devId, final BiConsumer _handler) { + public Client onActivation(BiConsumer _handler) { return onActivation(null, _handler); } @Override - public Client onDevice(final String _devId, final String _event, final TriConsumer _handler) { + public Client onDevice(String _devId, String _event, TriConsumer _handler) { if (connection != null) { throw new RuntimeException("Already connected"); } @@ -439,7 +446,7 @@ public Client onDevice(final String _devId, final String _event, final TriConsum } handlers.get(AbstractEventHandler.class).add(new AbstractEventHandler() { @Override - public void handle(String _devId, String _event, JSONObject _data) { + public void handle(String _devId, String _event, RawMessage _data) { _handler.accept(_devId, _event, _data); } @@ -457,12 +464,12 @@ public String getEvent() { } @Override - public Client onDevice(final String _devId, final TriConsumer _handler) { + public Client onDevice(String _devId, TriConsumer _handler) { return onDevice(_devId, null, _handler); } @Override - public Client onDevice(final TriConsumer _handler) { + public Client onDevice(TriConsumer _handler) { return onDevice(null, null, _handler); } } diff --git a/data/common/pom.xml b/data/data-common/pom.xml similarity index 83% rename from data/common/pom.xml rename to data/data-common/pom.xml index 63a5bdd..6103922 100644 --- a/data/common/pom.xml +++ b/data/data-common/pom.xml @@ -4,7 +4,7 @@ org.thethingsnetwork data - 2.0.0 + 2.1.0 data-common jar @@ -20,9 +20,9 @@ - org.json - json - 20160810 + com.fasterxml.jackson.core + jackson-databind + 2.8.3 diff --git a/data/common/src/main/java/org/thethingsnetwork/data/common/AbstractClient.java b/data/data-common/src/main/java/org/thethingsnetwork/data/common/AbstractClient.java similarity index 66% rename from data/common/src/main/java/org/thethingsnetwork/data/common/AbstractClient.java rename to data/data-common/src/main/java/org/thethingsnetwork/data/common/AbstractClient.java index ad41cde..733bdd9 100644 --- a/data/common/src/main/java/org/thethingsnetwork/data/common/AbstractClient.java +++ b/data/data-common/src/main/java/org/thethingsnetwork/data/common/AbstractClient.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,17 +23,32 @@ */ package org.thethingsnetwork.data.common; -import java.nio.ByteBuffer; +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; import java.util.function.BiConsumer; import java.util.function.Consumer; -import org.json.JSONObject; +import org.thethingsnetwork.data.common.messages.ActivationMessage; +import org.thethingsnetwork.data.common.messages.DataMessage; +import org.thethingsnetwork.data.common.messages.DownlinkMessage; +import org.thethingsnetwork.data.common.messages.RawMessage; /** * This is an abstract representation of the methods any real-time TTN client should provide * * @author Romain Cambier */ -public interface AbstractClient { +public abstract class AbstractClient { + + public static final ObjectMapper MAPPER = new ObjectMapper(); + + static { + MAPPER + .setVisibility(PropertyAccessor.ALL, Visibility.NONE) + .setVisibility(PropertyAccessor.FIELD, Visibility.ANY) + .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + } /** * Start the client @@ -41,7 +56,7 @@ public interface AbstractClient { * @return the Client instance * @throws Exception in case something goes wrong */ - public AbstractClient start() throws Exception; + public abstract AbstractClient start() throws Exception; /** * Stop the client, waiting for max 5000 ms @@ -49,7 +64,7 @@ public interface AbstractClient { * @return the Client instance * @throws Exception in case something goes wrong */ - public AbstractClient end() throws Exception; + public abstract AbstractClient end() throws Exception; /** * Stop the client, waiting for max the provided timeout @@ -58,7 +73,7 @@ public interface AbstractClient { * @return the Client instance * @throws Exception in case something goes wrong */ - public AbstractClient end(long _timeout) throws Exception; + public abstract AbstractClient end(long _timeout) throws Exception; /** * Force-stop the client in case end() does not work. @@ -66,37 +81,16 @@ public interface AbstractClient { * @return the Client instance * @throws Exception in case something goes wrong */ - public AbstractClient endNow() throws Exception; - - /** - * Send a downlink message using raw data - * - * @param _devId The devId to send the message to - * @param _payload The payload to be sent - * @param _port The port to use for the message - * @throws Exception in case something goes wrong - */ - public void send(String _devId, byte[] _payload, int _port) throws Exception; - - /** - * Send a downlink message using pre-registered encoder - * - * @param _devId The devId to send the message to - * @param _payload The payload to be sent - * @param _port The port to use for the message - * @throws Exception in case something goes wrong - */ - public void send(String _devId, JSONObject _payload, int _port) throws Exception; + public abstract AbstractClient endNow() throws Exception; /** * Send a downlink message using raw data * * @param _devId The devId to send the message to * @param _payload The payload to be sent - * @param _port The port to use for the message * @throws Exception in case something goes wrong */ - public void send(String _devId, ByteBuffer _payload, int _port) throws Exception; + public abstract void send(String _devId, DownlinkMessage _payload) throws Exception; /** * Register a connection event handler @@ -105,7 +99,7 @@ public interface AbstractClient { * @return the Connection instance * @throws Exception in case something goes wrong */ - public AbstractClient onConnected(final Consumer _handler) throws Exception; + public abstract AbstractClient onConnected(Consumer _handler) throws Exception; /** * Register an error event handler @@ -114,7 +108,7 @@ public interface AbstractClient { * @return the Client instance * @throws Exception in case something goes wrong */ - public AbstractClient onError(final Consumer _handler) throws Exception; + public abstract AbstractClient onError(Consumer _handler) throws Exception; /** * Register an uplink event handler using device and field filters @@ -125,7 +119,7 @@ public interface AbstractClient { * @return the Client instance * @throws Exception in case something goes wrong */ - public AbstractClient onMessage(final String _devId, final String _field, final BiConsumer _handler) throws Exception; + public abstract AbstractClient onMessage(String _devId, String _field, BiConsumer _handler) throws Exception; /** * Register an uplink event handler using device filter @@ -135,7 +129,7 @@ public interface AbstractClient { * @return the Client instance * @throws Exception in case something goes wrong */ - public AbstractClient onMessage(final String _devId, final BiConsumer _handler) throws Exception; + public abstract AbstractClient onMessage(String _devId, BiConsumer _handler) throws Exception; /** * Register an uplink event handler @@ -144,7 +138,7 @@ public interface AbstractClient { * @return the Client instance * @throws Exception in case something goes wrong */ - public AbstractClient onMessage(final BiConsumer _handler) throws Exception; + public abstract AbstractClient onMessage(BiConsumer _handler) throws Exception; /** * Register an activation event handler using device filter @@ -154,7 +148,7 @@ public interface AbstractClient { * @return the Client instance * @throws Exception in case something goes wrong */ - public AbstractClient onActivation(final String _devId, final BiConsumer _handler) throws Exception; + public abstract AbstractClient onActivation(String _devId, BiConsumer _handler) throws Exception; /** * Register an activation event handler @@ -163,7 +157,7 @@ public interface AbstractClient { * @return the Client instance * @throws Exception in case something goes wrong */ - public AbstractClient onActivation(final BiConsumer _handler) throws Exception; + public abstract AbstractClient onActivation(BiConsumer _handler) throws Exception; /** * Register a default event handler using device and event filters @@ -174,7 +168,7 @@ public interface AbstractClient { * @return the Client instance * @throws Exception in case something goes wrong */ - public AbstractClient onDevice(final String _devId, final String _event, final TriConsumer _handler) throws Exception; + public abstract AbstractClient onDevice(String _devId, String _event, TriConsumer _handler) throws Exception; /** * Register a default event handler using device filter @@ -184,7 +178,7 @@ public interface AbstractClient { * @return the Client instance * @throws Exception in case something goes wrong */ - public AbstractClient onDevice(final String _devId, final TriConsumer _handler) throws Exception; + public abstract AbstractClient onDevice(String _devId, TriConsumer _handler) throws Exception; /** * Register a default event handler @@ -193,6 +187,6 @@ public interface AbstractClient { * @return the Client instance * @throws Exception in case something goes wrong */ - public AbstractClient onDevice(final TriConsumer _handler) throws Exception; + public abstract AbstractClient onDevice(TriConsumer _handler) throws Exception; } diff --git a/data/common/src/main/java/org/thethingsnetwork/data/common/Connection.java b/data/data-common/src/main/java/org/thethingsnetwork/data/common/Connection.java similarity index 89% rename from data/common/src/main/java/org/thethingsnetwork/data/common/Connection.java rename to data/data-common/src/main/java/org/thethingsnetwork/data/common/Connection.java index 27bcb9b..f2a5a39 100644 --- a/data/common/src/main/java/org/thethingsnetwork/data/common/Connection.java +++ b/data/data-common/src/main/java/org/thethingsnetwork/data/common/Connection.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -29,6 +29,10 @@ */ public interface Connection { + /** + * Return the underlying Connection Object + * @return the underlying Connection + */ public Object get(); } diff --git a/data/data-common/src/main/java/org/thethingsnetwork/data/common/Metadata.java b/data/data-common/src/main/java/org/thethingsnetwork/data/common/Metadata.java new file mode 100644 index 0000000..e853967 --- /dev/null +++ b/data/data-common/src/main/java/org/thethingsnetwork/data/common/Metadata.java @@ -0,0 +1,183 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.data.common; + +import java.util.Collections; +import java.util.List; + +/** + * + * @author Romain Cambier + */ +public class Metadata { + + private String time; + private double frequency; + private String modulation; + private String dataRate; + private String bitRate; + private String codingRate; + private List gateways; + + private Metadata() { + + } + + /** + * Get the RX (uplink) or TX (downlink) time of this packet + * + * @return the time as a String + */ + public String getTime() { + return time; + } + + /** + * Get the frequency of this packet + * + * @return the frequency, in MHz + */ + public double getFrequency() { + return frequency; + } + + /** + * Get the Modulation of this packet + * + * @return the modulation + */ + public String getModulation() { + return modulation; + } + + /** + * Get the data rate of this packet + * + * @return the data rate + */ + public String getDataRate() { + return dataRate; + } + + /** + * Get the bit rate of this packet + * + * @return the bit rate + */ + public String getBitRate() { + return bitRate; + } + + /** + * Get the coding rate of this packet + * + * @return the coding rate + */ + public String getCodingRate() { + return codingRate; + } + + /** + * Get the list of gateways that received this packet + * @return a List of Gateway + */ + public List getGateways() { + if (gateways == null) { + return null; + } + return Collections.unmodifiableList(gateways); + } + + + public static class Gateway { + + private String id; + private long timestamp; + private String time; + private int channel; + private double rssi; + private double snr; + private int rfChain; + + private Gateway() { + + } + + /** + * Get the Gateway ID as registered in TheThingsNetwork + * @return the gateway id + */ + public String getId() { + return id; + } + + /** + * Get the Gateway internal reception time + * @return the gateway internal reception time + */ + public long getTimestamp() { + return timestamp; + } + + /** + * Get the Gateway absolute reception time + * @return the gateway absolute reception time + */ + public String getTime() { + return time; + } + + /** + * Get the channel this packet was sent on + * @return the channel this packet was sent on + */ + public int getChannel() { + return channel; + } + + /** + * Get the RX rssi of this packet + * @return the RX rssi of this packet + */ + public double getRssi() { + return rssi; + } + + /** + * Get the RX snr of this packet + * @return the RX snr of this packet + */ + public double getSnr() { + return snr; + } + + /** + * Get the RF chain of this packet + * @return the RF chain of this packet + */ + public int getRfChain() { + return rfChain; + } + } +} diff --git a/data/common/src/main/java/org/thethingsnetwork/data/common/Subscribable.java b/data/data-common/src/main/java/org/thethingsnetwork/data/common/Subscribable.java similarity index 96% rename from data/common/src/main/java/org/thethingsnetwork/data/common/Subscribable.java rename to data/data-common/src/main/java/org/thethingsnetwork/data/common/Subscribable.java index 59c66d5..f5998fe 100644 --- a/data/common/src/main/java/org/thethingsnetwork/data/common/Subscribable.java +++ b/data/data-common/src/main/java/org/thethingsnetwork/data/common/Subscribable.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,7 +24,6 @@ package org.thethingsnetwork.data.common; /** - * * @author Romain Cambier */ public interface Subscribable { diff --git a/account/src/main/java/org/thethingsnetwork/account/AbstractApplication.java b/data/data-common/src/main/java/org/thethingsnetwork/data/common/TriConsumer.java similarity index 70% rename from account/src/main/java/org/thethingsnetwork/account/AbstractApplication.java rename to data/data-common/src/main/java/org/thethingsnetwork/data/common/TriConsumer.java index 92682a1..1d27bd2 100644 --- a/account/src/main/java/org/thethingsnetwork/account/AbstractApplication.java +++ b/data/data-common/src/main/java/org/thethingsnetwork/data/common/TriConsumer.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,26 +21,14 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.thethingsnetwork.account; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.thethingsnetwork.account.auth.token.OAuth2Token; +package org.thethingsnetwork.data.common; /** * * @author Romain Cambier */ -@JsonIgnoreProperties(ignoreUnknown = true) -public interface AbstractApplication { - - public String getId(); - - public String getName(); - - public String getCreated(); - - public void setName(String _name); - - public void updateCredentials(OAuth2Token _creds); +@FunctionalInterface +public interface TriConsumer { + public void accept(T t, U u, V v); } diff --git a/data/common/src/main/java/org/thethingsnetwork/data/common/events/AbstractEventHandler.java b/data/data-common/src/main/java/org/thethingsnetwork/data/common/events/AbstractEventHandler.java similarity index 89% rename from data/common/src/main/java/org/thethingsnetwork/data/common/events/AbstractEventHandler.java rename to data/data-common/src/main/java/org/thethingsnetwork/data/common/events/AbstractEventHandler.java index 856ef0c..22c1e02 100644 --- a/data/common/src/main/java/org/thethingsnetwork/data/common/events/AbstractEventHandler.java +++ b/data/data-common/src/main/java/org/thethingsnetwork/data/common/events/AbstractEventHandler.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,16 +23,17 @@ */ package org.thethingsnetwork.data.common.events; -import org.json.JSONObject; import org.thethingsnetwork.data.common.Subscribable; +import org.thethingsnetwork.data.common.messages.RawMessage; /** + * Handler protoype for raw device events * * @author Romain Cambier */ public abstract class AbstractEventHandler implements EventHandler { - public abstract void handle(String _devId, String _event, JSONObject _data); + public abstract void handle(String _devId, String _event, RawMessage _data); public abstract String getDevId(); diff --git a/data/common/src/main/java/org/thethingsnetwork/data/common/events/ActivationHandler.java b/data/data-common/src/main/java/org/thethingsnetwork/data/common/events/ActivationHandler.java similarity index 88% rename from data/common/src/main/java/org/thethingsnetwork/data/common/events/ActivationHandler.java rename to data/data-common/src/main/java/org/thethingsnetwork/data/common/events/ActivationHandler.java index 40cbc2d..a4a260c 100644 --- a/data/common/src/main/java/org/thethingsnetwork/data/common/events/ActivationHandler.java +++ b/data/data-common/src/main/java/org/thethingsnetwork/data/common/events/ActivationHandler.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,16 +23,16 @@ */ package org.thethingsnetwork.data.common.events; -import org.json.JSONObject; import org.thethingsnetwork.data.common.Subscribable; +import org.thethingsnetwork.data.common.messages.ActivationMessage; /** - * + * Handler protoype for device activations * @author Romain Cambier */ public abstract class ActivationHandler implements EventHandler { - public abstract void handle(String _devId, JSONObject _data); + public abstract void handle(String _devId, ActivationMessage _data); public abstract String getDevId(); diff --git a/data/common/src/main/java/org/thethingsnetwork/data/common/events/ConnectHandler.java b/data/data-common/src/main/java/org/thethingsnetwork/data/common/events/ConnectHandler.java similarity index 94% rename from data/common/src/main/java/org/thethingsnetwork/data/common/events/ConnectHandler.java rename to data/data-common/src/main/java/org/thethingsnetwork/data/common/events/ConnectHandler.java index 3b56a6a..460d31c 100644 --- a/data/common/src/main/java/org/thethingsnetwork/data/common/events/ConnectHandler.java +++ b/data/data-common/src/main/java/org/thethingsnetwork/data/common/events/ConnectHandler.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -27,6 +27,7 @@ import org.thethingsnetwork.data.common.Subscribable; /** + * Handler protoype for dclient connection * * @author Romain Cambier */ diff --git a/data/common/src/main/java/org/thethingsnetwork/data/common/events/ErrorHandler.java b/data/data-common/src/main/java/org/thethingsnetwork/data/common/events/ErrorHandler.java similarity index 95% rename from data/common/src/main/java/org/thethingsnetwork/data/common/events/ErrorHandler.java rename to data/data-common/src/main/java/org/thethingsnetwork/data/common/events/ErrorHandler.java index d10d30a..8cc3b41 100644 --- a/data/common/src/main/java/org/thethingsnetwork/data/common/events/ErrorHandler.java +++ b/data/data-common/src/main/java/org/thethingsnetwork/data/common/events/ErrorHandler.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,6 +26,7 @@ import org.thethingsnetwork.data.common.Subscribable; /** + * Handler protoype for client errors * * @author Romain Cambier */ diff --git a/data/common/src/main/java/org/thethingsnetwork/data/common/events/EventHandler.java b/data/data-common/src/main/java/org/thethingsnetwork/data/common/events/EventHandler.java similarity index 96% rename from data/common/src/main/java/org/thethingsnetwork/data/common/events/EventHandler.java rename to data/data-common/src/main/java/org/thethingsnetwork/data/common/events/EventHandler.java index 0b54b75..b010f67 100644 --- a/data/common/src/main/java/org/thethingsnetwork/data/common/events/EventHandler.java +++ b/data/data-common/src/main/java/org/thethingsnetwork/data/common/events/EventHandler.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,11 +26,10 @@ import org.thethingsnetwork.data.common.Subscribable; /** - * * @author Romain Cambier */ public interface EventHandler { - + public void subscribe(Subscribable _client) throws Exception; - + } diff --git a/data/common/src/main/java/org/thethingsnetwork/data/common/events/UplinkHandler.java b/data/data-common/src/main/java/org/thethingsnetwork/data/common/events/UplinkHandler.java similarity index 77% rename from data/common/src/main/java/org/thethingsnetwork/data/common/events/UplinkHandler.java rename to data/data-common/src/main/java/org/thethingsnetwork/data/common/events/UplinkHandler.java index 4f8e77c..8c32207 100644 --- a/data/common/src/main/java/org/thethingsnetwork/data/common/events/UplinkHandler.java +++ b/data/data-common/src/main/java/org/thethingsnetwork/data/common/events/UplinkHandler.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,30 +23,35 @@ */ package org.thethingsnetwork.data.common.events; -import org.thethingsnetwork.data.common.Message; import org.thethingsnetwork.data.common.Subscribable; +import org.thethingsnetwork.data.common.messages.DataMessage; /** + * Handler protoype for device uplink messages * * @author Romain Cambier */ public abstract class UplinkHandler implements EventHandler { - public abstract void handle(String _devId, Object _data); + public abstract void handle(String _devId, DataMessage _data); public abstract String getDevId(); public abstract String getField(); - public boolean matches(String _devId) { - return getDevId() == null || _devId.equals(getDevId()); + public boolean isField() { + return getField() != null; } - public Object transform(String _data) { - if (getField() == null) { - return new Message(_data); + public boolean matches(String _devId, String _field) { + if (getDevId() != null && !getDevId().equals(_devId)) { + return false; + } + if (getField() != null) { + return !(_field == null || !getField().equals(_field)); + } else { + return _field == null; } - return _data; } @Override diff --git a/data/data-common/src/main/java/org/thethingsnetwork/data/common/messages/ActivationMessage.java b/data/data-common/src/main/java/org/thethingsnetwork/data/common/messages/ActivationMessage.java new file mode 100644 index 0000000..ff87522 --- /dev/null +++ b/data/data-common/src/main/java/org/thethingsnetwork/data/common/messages/ActivationMessage.java @@ -0,0 +1,80 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.data.common.messages; + +import org.thethingsnetwork.data.common.Metadata; + +/** + * This is a wrapper for activation messages + * + * @author Romain Cambier + */ +public class ActivationMessage { + + private String appEui; + private String devEui; + private String devAddr; + private Metadata metadata; + + private ActivationMessage() { + + } + + /** + * Get the LoraWan Application EUI + * + * @return the LoraWan Application EUI + */ + public String getAppEui() { + return appEui; + } + + /** + * Get the LoraWan Device EUI + * + * @return the LoraWan Device EUI + */ + public String getDevEui() { + return devEui; + } + + /** + * Get the LoraWan Device address + * + * @return the LoraWan Device address + */ + public String getDevAddr() { + return devAddr; + } + + /** + * Get the metadata of this uplink packet + * + * @return the metadata + */ + public Metadata getMetadata() { + return metadata; + } + +} diff --git a/account/src/main/java/org/thethingsnetwork/account/auth/token/OAuth2Token.java b/data/data-common/src/main/java/org/thethingsnetwork/data/common/messages/DataMessage.java similarity index 74% rename from account/src/main/java/org/thethingsnetwork/account/auth/token/OAuth2Token.java rename to data/data-common/src/main/java/org/thethingsnetwork/data/common/messages/DataMessage.java index 7ad9164..643bcad 100644 --- a/account/src/main/java/org/thethingsnetwork/account/auth/token/OAuth2Token.java +++ b/data/data-common/src/main/java/org/thethingsnetwork/data/common/messages/DataMessage.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,27 +21,12 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.thethingsnetwork.account.auth.token; - -import java.net.URI; -import rx.Observable; +package org.thethingsnetwork.data.common.messages; /** - * + * Wrapper for any sort of uplink message * @author Romain Cambier */ -public interface OAuth2Token { - - public boolean hasRefresh(); - - public Observable refresh(); +public interface DataMessage { - public boolean isExpired(); - - public String getToken(); - - public String getRawToken(); - - public URI getAccountServer(); - } diff --git a/data/data-common/src/main/java/org/thethingsnetwork/data/common/messages/DownlinkMessage.java b/data/data-common/src/main/java/org/thethingsnetwork/data/common/messages/DownlinkMessage.java new file mode 100644 index 0000000..edfbd02 --- /dev/null +++ b/data/data-common/src/main/java/org/thethingsnetwork/data/common/messages/DownlinkMessage.java @@ -0,0 +1,90 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.data.common.messages; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import java.nio.ByteBuffer; +import java.util.Base64; + +/** + * Wrapper for a downlink message + * + * @author Romain Cambier + */ +@JsonInclude(Include.NON_NULL) +public class DownlinkMessage { + + private int port; + private String payloadRaw; + private Object payloadFields; + + /** + * Constructor for a base64-encoded payload + * + * @param _port the port to be used while encrypting the message + * @param _payload the actual base64 string + */ + public DownlinkMessage(int _port, String _payload) { + port = _port; + payloadRaw = _payload; + } + + /** + * Constructor for a byte array + * + * @param _port the port to be used while encrypting the message + * @param _payload the actual data + */ + public DownlinkMessage(int _port, byte[] _payload) { + port = _port; + payloadRaw = Base64.getEncoder().encodeToString(_payload); + } + + /** + * Constructor for a byte buffer + * + * @param _port the port to be used while encrypting the message + * @param _payload the actual data + */ + public DownlinkMessage(int _port, ByteBuffer _payload) { + port = _port; + _payload.rewind(); + byte[] payload = new byte[_payload.capacity() - _payload.remaining()]; + _payload.get(payload); + payloadRaw = Base64.getEncoder().encodeToString(payload); + } + + /** + * Constructor for any kind of java object that will be json-serialized by jackson + * + * @param _port the port to be used while encrypting the message + * @param _payload the actual object + */ + public DownlinkMessage(int _port, Object _payload) { + port = _port; + payloadFields = _payload; + } + +} diff --git a/data/data-common/src/main/java/org/thethingsnetwork/data/common/messages/RawMessage.java b/data/data-common/src/main/java/org/thethingsnetwork/data/common/messages/RawMessage.java new file mode 100644 index 0000000..561a407 --- /dev/null +++ b/data/data-common/src/main/java/org/thethingsnetwork/data/common/messages/RawMessage.java @@ -0,0 +1,99 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.data.common.messages; + +import java.io.IOException; +import org.thethingsnetwork.data.common.AbstractClient; + +/** + * Wrapper for a filtered message payload (ex: only one field of the decoded json) + * + * @author Romain Cambier + */ +public abstract class RawMessage implements DataMessage { + + /** + * Get the payload as a String + * + * @return the payload as a String + */ + public abstract String asString(); + + /** + * Get the payload as an Integer + * + * @return the payload as an Integer + */ + public int asInt() { + return Integer.parseInt(asString()); + } + + /** + * Get the payload as a Double + * + * @return the payload as a Double + */ + public double asDouble() { + return Double.parseDouble(asString()); + } + + /** + * Get the payload as a Boolean + * + * @return the payload as a Boolean + */ + public boolean asBoolean() { + return Boolean.parseBoolean(asString()); + } + + /** + * Get the payload as a custom Object + * The custom object can be a default one as Boolean, Integer, Double, or String. + * In case it's something different, jackson will be used to parse the payload + * + * @param a Boolean, Integer, Double, String, or the custom user-provided class + * @param _class the type of object to deserialize the payload to. + * @return the payload as a custom Object + * @throws java.io.IOException in case the deserialization could not be done + */ + public T as(Class _class) throws IOException { + if (_class == null) { + throw new NullPointerException(); + } + if (_class.equals(Boolean.class)) { + return (T) (Boolean) asBoolean(); + } + if (_class.equals(Integer.class)) { + return (T) (Integer) asInt(); + } + if (_class.equals(Double.class)) { + return (T) (Double) asDouble(); + } + if (_class.equals(String.class)) { + return (T) asString(); + } + return AbstractClient.MAPPER.readValue(asString(), _class); + } + +} diff --git a/data/data-common/src/main/java/org/thethingsnetwork/data/common/messages/UplinkMessage.java b/data/data-common/src/main/java/org/thethingsnetwork/data/common/messages/UplinkMessage.java new file mode 100644 index 0000000..73a332c --- /dev/null +++ b/data/data-common/src/main/java/org/thethingsnetwork/data/common/messages/UplinkMessage.java @@ -0,0 +1,91 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.data.common.messages; + +import java.util.Base64; +import java.util.Collections; +import java.util.Map; +import org.thethingsnetwork.data.common.Metadata; + +/** + * This is a wrapper class for JSONObject to provide support for base64 encoded payload + * + * @author Romain Cambier + */ +public class UplinkMessage implements DataMessage { + + private int port; + private int counter; + private String payloadRaw; + private Map payloadFields; + private Metadata metadata; + + private UplinkMessage() { + + } + + /** + * Get the encryption port + * + * @return the encryption port + */ + public int getPort() { + return port; + } + + /** + * Get the uplink frame counter + * + * @return the uplink frame counter + */ + public int getCounter() { + return counter; + } + + /** + * Get the raw payload + * + * @return the raw payload as a byte array + */ + public byte[] getPayloadRaw() { + return Base64.getDecoder().decode(payloadRaw); + } + + /** + * Get the payload fields. Only if you have a decoder function + * @return the payload fields as a Map where keys are strings, and values are any json-valid entity + */ + public Map getPayloadFields() { + return Collections.unmodifiableMap(payloadFields); + } + + /** + * Get the metadata of this uplink packet + * @return the metadata + */ + public Metadata getMetadata() { + return metadata; + } + +} diff --git a/data/mqtt/API.md b/data/data-mqtt/API.md similarity index 100% rename from data/mqtt/API.md rename to data/data-mqtt/API.md diff --git a/data/mqtt/README.md b/data/data-mqtt/README.md similarity index 100% rename from data/mqtt/README.md rename to data/data-mqtt/README.md diff --git a/data/mqtt/pom.xml b/data/data-mqtt/pom.xml similarity index 97% rename from data/mqtt/pom.xml rename to data/data-mqtt/pom.xml index 7e4a128..4379cdd 100644 --- a/data/mqtt/pom.xml +++ b/data/data-mqtt/pom.xml @@ -4,7 +4,7 @@ org.thethingsnetwork data - 2.0.0 + 2.1.0 data-mqtt jar diff --git a/data/mqtt/src/main/java/org/thethingsnetwork/data/mqtt/Client.java b/data/data-mqtt/src/main/java/org/thethingsnetwork/data/mqtt/Client.java similarity index 77% rename from data/mqtt/src/main/java/org/thethingsnetwork/data/mqtt/Client.java rename to data/data-mqtt/src/main/java/org/thethingsnetwork/data/mqtt/Client.java index 1835582..9335384 100644 --- a/data/mqtt/src/main/java/org/thethingsnetwork/data/mqtt/Client.java +++ b/data/data-mqtt/src/main/java/org/thethingsnetwork/data/mqtt/Client.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,8 +25,6 @@ import java.net.URI; import java.net.URISyntaxException; -import java.nio.ByteBuffer; -import java.util.Base64; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -45,7 +43,6 @@ import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; -import org.json.JSONObject; import org.thethingsnetwork.data.common.AbstractClient; import org.thethingsnetwork.data.common.Connection; import org.thethingsnetwork.data.common.Subscribable; @@ -56,13 +53,18 @@ import org.thethingsnetwork.data.common.events.ErrorHandler; import org.thethingsnetwork.data.common.events.EventHandler; import org.thethingsnetwork.data.common.events.UplinkHandler; +import org.thethingsnetwork.data.common.messages.ActivationMessage; +import org.thethingsnetwork.data.common.messages.DataMessage; +import org.thethingsnetwork.data.common.messages.DownlinkMessage; +import org.thethingsnetwork.data.common.messages.RawMessage; +import org.thethingsnetwork.data.common.messages.UplinkMessage; /** * This is the base class to be used to interact with The Things Network Handler * * @author Romain Cambier */ -public class Client implements AbstractClient { +public class Client extends AbstractClient { /** * Connection settings @@ -162,7 +164,7 @@ public Client start() throws MqttException, Exception { mqttClient.connect(connOpts); mqttClient.setCallback(new MqttCallback() { @Override - public void connectionLost(final Throwable cause) { + public void connectionLost(Throwable cause) { mqttClient = null; if (handlers.containsKey(ErrorHandler.class)) { handlers.get(ErrorHandler.class).stream().forEach((handler) -> { @@ -174,32 +176,50 @@ public void connectionLost(final Throwable cause) { } @Override - public void messageArrived(String topic, final MqttMessage message) throws Exception { - final String[] tokens = topic.split("\\/"); + public void messageArrived(String topic, MqttMessage message) throws Exception { + String[] tokens = topic.split("\\/"); if (tokens.length < 4) { return; } switch (tokens[3]) { case "up": if (handlers.containsKey(UplinkHandler.class)) { - handlers.get(UplinkHandler.class).stream().forEach((handler) -> { - executor.submit(() -> { - try { - UplinkHandler uh = (UplinkHandler) handler; - if (uh.matches(tokens[2])) { - uh.handle(tokens[2], uh.transform(new String(message.getPayload()))); - } - } catch (final Exception ex) { - if (handlers.containsKey(ErrorHandler.class)) { - handlers.get(ErrorHandler.class).stream().forEach((handler1) -> { - executor.submit(() -> { - ((ErrorHandler) handler1).safelyHandle(ex); - }); - }); - } - } - }); - }); + String field; + if (tokens.length > 4) { + field = concat(4, tokens); + } else { + field = null; + } + handlers.get(UplinkHandler.class).stream() + .forEach((handler) -> { + executor.submit(() -> { + try { + UplinkHandler uh = (UplinkHandler) handler; + if (uh.matches(tokens[2], field)) { + if (uh.isField()) { + uh.handle(tokens[2], new RawMessage() { + String str = new String(message.getPayload()); + + @Override + public String asString() { + return str; + } + }); + } else { + uh.handle(tokens[2], MAPPER.readValue(message.getPayload(), UplinkMessage.class)); + } + } + } catch (Exception ex) { + if (handlers.containsKey(ErrorHandler.class)) { + handlers.get(ErrorHandler.class).stream().forEach((handler1) -> { + executor.submit(() -> { + ((ErrorHandler) handler1).safelyHandle(ex); + }); + }); + } + } + }); + }); } break; case "events": @@ -212,9 +232,9 @@ public void messageArrived(String topic, final MqttMessage message) throws Excep try { ActivationHandler ah = (ActivationHandler) handler; if (ah.matches(tokens[2])) { - ah.handle(tokens[2], new JSONObject(new String(message.getPayload()))); + ah.handle(tokens[2], MAPPER.readValue(message.getPayload(), ActivationMessage.class)); } - } catch (final Exception ex) { + } catch (Exception ex) { if (handlers.containsKey(ErrorHandler.class)) { handlers.get(ErrorHandler.class).stream().forEach((handler1) -> { executor.submit(() -> { @@ -235,9 +255,16 @@ public void messageArrived(String topic, final MqttMessage message) throws Excep AbstractEventHandler aeh = (AbstractEventHandler) handler; String event = concat(4, tokens); if (aeh.matches(tokens[2], event)) { - aeh.handle(tokens[2], event, new JSONObject(new String(message.getPayload()))); + aeh.handle(tokens[2], event, new RawMessage() { + String str = new String(message.getPayload()); + + @Override + public String asString() { + return str; + } + }); } - } catch (final Exception ex) { + } catch (Exception ex) { if (handlers.containsKey(ErrorHandler.class)) { handlers.get(ErrorHandler.class).stream().forEach((handler1) -> { executor.submit(() -> { @@ -297,7 +324,7 @@ public String getPathWildcard() { executor.submit(() -> { try { ((ConnectHandler) handler).handle(() -> mqttClient); - } catch (final Exception ex) { + } catch (Exception ex) { if (handlers.containsKey(ErrorHandler.class)) { handlers.get(ErrorHandler.class).stream().forEach((handler1) -> { executor.submit(() -> { @@ -352,31 +379,12 @@ public Client endNow() throws MqttException { } @Override - public void send(String _devId, byte[] _payload, int _port) throws MqttException { - JSONObject data = new JSONObject(); - data.put("payload_raw", Base64.getEncoder().encodeToString(_payload)); - data.put("port", _port != 0 ? _port : 1); - mqttClient.publish(appId + "/devices/" + _devId + "/down", data.toString().getBytes(), 0, false); - } - - @Override - public void send(String _devId, JSONObject _payload, int _port) throws MqttException { - JSONObject data = new JSONObject(); - data.put("payload_fields", _payload); - data.put("port", _port != 0 ? _port : 1); - mqttClient.publish(appId + "/devices/" + _devId + "/down", data.toString().getBytes(), 0, false); - } - - @Override - public void send(String _devId, ByteBuffer _payload, int _port) throws MqttException { - _payload.rewind(); - byte[] payload = new byte[_payload.capacity() - _payload.remaining()]; - _payload.get(payload); - send(_devId, payload, _port); + public void send(String _devId, DownlinkMessage _payload) throws Exception { + mqttClient.publish(appId + "/devices/" + _devId + "/down", MAPPER.writeValueAsBytes(_payload), 0, false); } @Override - public Client onConnected(final Consumer _handler) { + public Client onConnected(Consumer _handler) { if (mqttClient != null) { throw new RuntimeException("Already connected"); } @@ -393,7 +401,7 @@ public void handle(Connection _client) { } @Override - public Client onError(final Consumer _handler) { + public Client onError(Consumer _handler) { if (mqttClient != null) { throw new RuntimeException("Already connected"); } @@ -410,7 +418,7 @@ public void handle(Throwable _error) { } @Override - public Client onMessage(final String _devId, final String _field, final BiConsumer _handler) { + public Client onMessage(String _devId, String _field, BiConsumer _handler) { if (mqttClient != null) { throw new RuntimeException("Already connected"); } @@ -419,7 +427,7 @@ public Client onMessage(final String _devId, final String _field, final BiConsum } handlers.get(UplinkHandler.class).add(new UplinkHandler() { @Override - public void handle(String _devId, Object _data) { + public void handle(String _devId, DataMessage _data) { _handler.accept(_devId, _data); } @@ -437,17 +445,17 @@ public String getField() { } @Override - public Client onMessage(final String _devId, final BiConsumer _handler) { + public Client onMessage(String _devId, BiConsumer _handler) { return onMessage(_devId, null, _handler); } @Override - public Client onMessage(final BiConsumer _handler) { + public Client onMessage(BiConsumer _handler) { return onMessage(null, null, _handler); } @Override - public Client onActivation(final String _devId, final BiConsumer _handler) { + public Client onActivation(String _devId, BiConsumer _handler) { if (mqttClient != null) { throw new RuntimeException("Already connected"); } @@ -456,7 +464,7 @@ public Client onActivation(final String _devId, final BiConsumer _handler) { + public Client onActivation(BiConsumer _handler) { return onActivation(null, _handler); } @Override - public Client onDevice(final String _devId, final String _event, final TriConsumer _handler) { + public Client onDevice(String _devId, String _event, TriConsumer _handler) { if (mqttClient != null) { throw new RuntimeException("Already connected"); } @@ -483,7 +491,7 @@ public Client onDevice(final String _devId, final String _event, final TriConsum } handlers.get(AbstractEventHandler.class).add(new AbstractEventHandler() { @Override - public void handle(String _devId, String _event, JSONObject _data) { + public void handle(String _devId, String _event, RawMessage _data) { _handler.accept(_devId, _event, _data); } @@ -501,12 +509,12 @@ public String getEvent() { } @Override - public Client onDevice(final String _devId, final TriConsumer _handler) { + public Client onDevice(String _devId, TriConsumer _handler) { return onDevice(_devId, null, _handler); } @Override - public Client onDevice(final TriConsumer _handler) { + public Client onDevice(TriConsumer _handler) { return onDevice(null, null, _handler); } diff --git a/data/pom.xml b/data/pom.xml index de93d11..8eb5b9b 100644 --- a/data/pom.xml +++ b/data/pom.xml @@ -4,15 +4,15 @@ org.thethingsnetwork app-sdk - 2.0.0 + 2.1.0 data pom - mqtt - amqp - common + data-mqtt + data-amqp + data-common The Things Network Data SDK diff --git a/management/pom.xml b/management/pom.xml index 38b228e..31b4baf 100644 --- a/management/pom.xml +++ b/management/pom.xml @@ -4,7 +4,7 @@ org.thethingsnetwork app-sdk - 2.0.0 + 2.1.0 management jar diff --git a/management/src/main/java/org/thethingsnetwork/management/HandlerApplication.java b/management/src/main/java/org/thethingsnetwork/management/HandlerApplication.java index 79c27c1..d6905df 100644 --- a/management/src/main/java/org/thethingsnetwork/management/HandlerApplication.java +++ b/management/src/main/java/org/thethingsnetwork/management/HandlerApplication.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,35 +28,15 @@ import rx.Subscriber; /** - * + * This class is a representation of a The Things Network application * @author Romain Cambier */ public class HandlerApplication { - private String appId; + private final String appId; private String decoder; private String converter; private String validator; - - public String getAppId() { - return appId; - } - - public String getDecoder() { - return decoder; - } - - public String getConverter() { - return converter; - } - - public String getValidator() { - return validator; - } - - public String getEncoder() { - return encoder; - } private String encoder; private HandlerApplication(String _appId, String _decoder, String _converter, String _validator, String _encoder) { @@ -67,6 +47,12 @@ private HandlerApplication(String _appId, String _decoder, String _converter, St encoder = _encoder; } + /** + * Build a HandlerApplication instance from a grpc representation + * + * @param _proto The grpc representation + * @return An Observable HandlerApplication containing the HandlerApplication instance + */ public static Observable from(HandlerOuterClass.Application _proto) { return Observable @@ -87,6 +73,11 @@ public static Observable from(HandlerOuterClass.Application } + /** + * Convert this HandlerApplication instance to the grpc representation + * + * @return The grpc representation + */ public Observable toProto() { return Observable @@ -107,4 +98,85 @@ public Observable toProto() { }); } + + /** + * Get the application id + * + * @return The application id + */ + public String getAppId() { + return appId; + } + + /** + * Get the application decoder function + * + * @return The applicationn decoder function + */ + public String getDecoder() { + return decoder; + } + + /** + * Get the application converter function + * + * @return The applicationn converter function + */ + public String getConverter() { + return converter; + } + + /** + * Get the application validator function + * + * @return The applicationn validator function + */ + public String getValidator() { + return validator; + } + + /** + * Get the application encoder function + * + * @return The applicationn encoder function + */ + public String getEncoder() { + return encoder; + } + + /** + * Set the application decoder function + * + * @param _decoder The applicationn decoder function + */ + public void setDecoder(String _decoder) { + decoder = _decoder; + } + + /** + * Set the application converter function + * + * @param _converter The converter function + */ + public void setConverter(String _converter) { + converter = _converter; + } + + /** + * Set the application validator function + * + * @param _validator The validator function + */ + public void setValidator(String _validator) { + validator = _validator; + } + + /** + * Set the application encoder function + * + * @param _encoder The encoder function + */ + public void setEncoder(String _encoder) { + encoder = _encoder; + } } diff --git a/management/src/main/java/org/thethingsnetwork/management/HandlerDevice.java b/management/src/main/java/org/thethingsnetwork/management/HandlerDevice.java index e57b0d4..821a97b 100644 --- a/management/src/main/java/org/thethingsnetwork/management/HandlerDevice.java +++ b/management/src/main/java/org/thethingsnetwork/management/HandlerDevice.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,10 +23,105 @@ */ package org.thethingsnetwork.management; +import org.thethingsnetwork.management.proto.DeviceOuterClass; +import org.thethingsnetwork.management.proto.HandlerOuterClass; +import rx.Observable; +import rx.Subscriber; + /** + * This class is a representation of a The Things Network device * * @author Romain Cambier */ public class HandlerDevice { - + + private final String appId; + private final String devId; + private final LorawanDevice lorawan; + + /** + * Build a device from application id, device id, and lorawan data + * + * @param _appId The application id + * @param _devId The device id + * @param _lorawan The LoraWan data + */ + public HandlerDevice(String _appId, String _devId, LorawanDevice _lorawan) { + appId = _appId; + devId = _devId; + lorawan = _lorawan; + } + + /** + * Get the application id + * + * @return The application id + */ + public String getAppId() { + return appId; + } + + /** + * Get the device id + * + * @return The device id + */ + public String getDevId() { + return devId; + } + + /** + * Get the LoraWan data + * + * @return the LoraWan data + */ + public LorawanDevice getLorawan() { + return lorawan; + } + + public static Observable from(HandlerOuterClass.Device _proto) { + + return LorawanDevice.from(_proto.getLorawanDevice()) + .flatMap((LorawanDevice tt) -> Observable + .create((Subscriber t) -> { + try { + t.onNext(new HandlerDevice( + _proto.getAppId(), + _proto.getDevId(), + tt + )); + t.onCompleted(); + } catch (Exception ex) { + t.onError(ex); + } + }) + ); + + } + + /** + * Convert this device to the grpc representation + * + * @return The grpc representation + */ + public Observable toProto() { + + return lorawan.toProto() + .flatMap((DeviceOuterClass.Device tt) -> Observable + .create((Subscriber t) -> { + try { + t.onNext(HandlerOuterClass.Device.newBuilder() + .setAppId(appId) + .setDevId(devId) + .setLorawanDevice(tt) + .build() + ); + t.onCompleted(); + } catch (Exception ex) { + t.onError(ex); + } + })); + + } + } diff --git a/management/src/main/java/org/thethingsnetwork/management/LorawanDevice.java b/management/src/main/java/org/thethingsnetwork/management/LorawanDevice.java new file mode 100644 index 0000000..6e07bce --- /dev/null +++ b/management/src/main/java/org/thethingsnetwork/management/LorawanDevice.java @@ -0,0 +1,434 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.management; + +import com.google.protobuf.ByteString; +import org.thethingsnetwork.management.proto.DeviceOuterClass; +import rx.Observable; +import rx.Subscriber; + +/** + * This class is a representation of a The Things Network LoraWan device + * + * @author Romain Cambier + */ +public class LorawanDevice { + + private byte[] appEui; + private byte[] devEui; + private final String appId; + private final String devId; + private byte[] devAddr; + private byte[] nwkSKey; + private byte[] appSKey; + private byte[] appKey; + private int fCntUp; + private int fCntDown; + private boolean disableFCntCheck; + private boolean uses32BitFCnt; + private String activationConstraints; + private final long lastSeen; + + private LorawanDevice(byte[] _appEui, byte[] _devEui, String _appId, String _devId, byte[] _devAddr, byte[] _nwkSKey, byte[] _appSKey, byte[] _appKey, int _fCntUp, int _fCntDown, boolean _disableFCntCheck, boolean _uses32BitFCnt, String _activationConstraints, long _lastSeen) { + appEui = _appEui; + devEui = _devEui; + appId = _appId; + devId = _devId; + devAddr = _devAddr; + nwkSKey = _nwkSKey; + appSKey = _appSKey; + appKey = _appKey; + fCntUp = _fCntUp; + fCntDown = _fCntDown; + disableFCntCheck = _disableFCntCheck; + uses32BitFCnt = _uses32BitFCnt; + activationConstraints = _activationConstraints; + lastSeen = _lastSeen; + } + + /** + * Create OTAA (over the air activation) LoraWan data + * + * @param _appId The application id + * @param _devId The device id + * @param _appEui The application EUI + * @param _devEui The device EUI + * @param _appKey The application key + * @return The corresponding LoraWan data + */ + public static LorawanDevice createOTAA(String _appId, String _devId, byte[] _appEui, byte[] _devEui, byte[] _appKey) { + if (_appEui.length != 8) { + throw new IllegalArgumentException("appEui should be 8 bytes long"); + } + if (_devEui.length != 8) { + throw new IllegalArgumentException("devEui should be 8 bytes long"); + } + if (_appKey.length != 16) { + throw new IllegalArgumentException("appKey should be 16 bytes long"); + } + return new LorawanDevice(_appEui, _devEui, _appId, _devId, null, null, null, _appKey, 0, 0, false, true, "otaa", 0); + } + + /** + * Create ABP (activation by personalization) LoraWan data + * + * @param _appId The application id + * @param _devId The device id + * @param _appEui The application EUI + * @param _devEui The device EUI + * @param _devAddr The device address + * @param _nwkSKey The network session key + * @param _appSKey The application session key + * @param _disableFCntCheck Whether if you want to disable fCnt check or not + * @param _uses32BitFCnt Whether to use 32 bits frame counters or not + * @return The corresponding LoraWan data + */ + public static LorawanDevice createABP(String _appId, String _devId, byte[] _appEui, byte[] _devEui, byte[] _devAddr, byte[] _nwkSKey, byte[] _appSKey, boolean _disableFCntCheck, boolean _uses32BitFCnt) { + if (_appEui.length != 8) { + throw new IllegalArgumentException("appEui should be 8 bytes long"); + } + if (_devEui.length != 8) { + throw new IllegalArgumentException("devEui should be 8 bytes long"); + } + if (_devAddr.length != 4) { + throw new IllegalArgumentException("devAddr should be 4 bytes long"); + } + if (_nwkSKey.length != 16) { + throw new IllegalArgumentException("nwkSKey should be 16 bytes long"); + } + if (_appSKey.length != 16) { + throw new IllegalArgumentException("appSKey should be 16 bytes long"); + } + return new LorawanDevice(_appEui, _devEui, _appId, _devId, _devAddr, _nwkSKey, _appSKey, null, 0, 0, _disableFCntCheck, _uses32BitFCnt, "abp", 0); + } + + /** + * Build a LorawanDevice instance from a grpc representation + * + * @param _proto The grpc representation + * @return An Observable LorawanDevice containing the LorawanDevice instance + */ + public static Observable from(DeviceOuterClass.Device _proto) { + + return Observable + .create((Subscriber t) -> { + try { + t.onNext(new LorawanDevice( + _proto.getAppEui().toByteArray(), + _proto.getDevEui().toByteArray(), + _proto.getAppId(), + _proto.getDevId(), + _proto.getDevAddr().toByteArray(), + _proto.getNwkSKey().toByteArray(), + _proto.getAppSKey().toByteArray(), + _proto.getAppKey().toByteArray(), + _proto.getFCntUp(), + _proto.getFCntDown(), + _proto.getDisableFCntCheck(), + _proto.getUses32BitFCnt(), + _proto.getActivationConstraints(), + _proto.getLastSeen() + )); + t.onCompleted(); + } catch (Exception ex) { + t.onError(ex); + } + }); + } + + /** + * Convert this device to the grpc representation + * + * @return The grpc representation + */ + public Observable toProto() { + + return Observable + .create((Subscriber t) -> { + try { + t.onNext(DeviceOuterClass.Device.newBuilder() + .setAppEui(ByteString.copyFrom(appEui)) + .setDevEui(ByteString.copyFrom(devEui)) + .setAppId(appId) + .setDevId(appId) + .setDevAddr(ByteString.copyFrom(devAddr)) + .setNwkSKey(ByteString.copyFrom(nwkSKey)) + .setAppSKey(ByteString.copyFrom(appSKey)) + .setAppKey(ByteString.copyFrom(appKey)) + .setFCntUp(fCntUp) + .setFCntDown(fCntDown) + .setDisableFCntCheck(disableFCntCheck) + .setUses32BitFCnt(uses32BitFCnt) + .setActivationConstraints(activationConstraints) + .setLastSeen(lastSeen) + .build() + ); + t.onCompleted(); + } catch (Exception ex) { + t.onError(ex); + } + }); + + } + + /** + * Get the application EUI + * + * @return the application EUI + */ + public byte[] getAppEui() { + return appEui; + } + + /** + * Set the application EUI + * + * @param _appEui the application EUI + */ + public void setAppEui(byte[] _appEui) { + if (_appEui.length != 8) { + throw new IllegalArgumentException("appEui should be 8 bytes long"); + } + appEui = _appEui; + } + + /** + * Get the device EUI + * + * @return The device EUI + */ + public byte[] getDevEui() { + return devEui; + } + + /** + * Set the device EUI + * + * @param _devEui the device EUI + */ + public void setDevEui(byte[] _devEui) { + if (_devEui.length != 8) { + throw new IllegalArgumentException("devEui should be 8 bytes long"); + } + devEui = _devEui; + } + + /** + * Get the application id + * + * @return The application id + */ + public String getAppId() { + return appId; + } + + /** + * Get the device id + * + * @return The device id + */ + public String getDevId() { + return devId; + } + + /** + * Get the device address + * + * @return The device address + */ + public byte[] getDevAddr() { + return devAddr; + } + + /** + * Set the device address + * + * @param _devAddr The device address + */ + public void setDevAddr(byte[] _devAddr) { + if (_devAddr.length != 4) { + throw new IllegalArgumentException("devAddr should be 4 bytes long"); + } + devAddr = _devAddr; + } + + /** + * Get the network session key + * + * @return The network session key + */ + public byte[] getNwkSKey() { + return nwkSKey; + } + + /** + * Set the network session key + * + * @param _nwkSKey The network session key + */ + public void setNwkSKey(byte[] _nwkSKey) { + if (_nwkSKey.length != 16) { + throw new IllegalArgumentException("nwkSKey should be 16 bytes long"); + } + nwkSKey = _nwkSKey; + } + + /** + * Get the application session key + * + * @return The application session key + */ + public byte[] getAppSKey() { + return appSKey; + } + + /** + * Set the application session key + * + * @param _appSKey The application session key + */ + public void setAppSKey(byte[] _appSKey) { + if (_appSKey.length != 16) { + throw new IllegalArgumentException("appSKey should be 16 bytes long"); + } + appSKey = _appSKey; + } + + /** + * Get the application key + * + * @return The application key + */ + public byte[] getAppKey() { + return appKey; + } + + /** + * Set the application key + * + * @param _appKey The application key + */ + public void setAppKey(byte[] _appKey) { + if (_appKey.length != 16) { + throw new IllegalArgumentException("appKey should be 16 bytes long"); + } + appKey = _appKey; + } + + /** + * Get the uplink frame counter + * + * @return The uplink frame counter + */ + public int getfCntUp() { + return fCntUp; + } + + /** + * Get the downlink frame counter + * + * @return The downlink frame counter + */ + public int getfCntDown() { + return fCntDown; + } + + /** + * Check if fCnt check is enabled + * + * @return True if fCnt check is enabled + */ + public boolean isDisableFCntCheck() { + return disableFCntCheck; + } + + /** + * Enable/Disable fCnt check + * + * @param _disableFCntCheck True to enable, false to disable + */ + public void setDisableFCntCheck(boolean _disableFCntCheck) { + disableFCntCheck = _disableFCntCheck; + } + + /** + * Check if 32 bit frame counter are used + * + * @return True if 32 bit frame counter are used + */ + public boolean isUses32BitFCnt() { + return uses32BitFCnt; + } + + /** + * Enable/Disable 32 bit frame counter + * + * @param _uses32BitFCnt True to enable 32 bit frame counter + */ + public void setUses32BitFCnt(boolean _uses32BitFCnt) { + uses32BitFCnt = _uses32BitFCnt; + } + + /** + * Get the activation constraints + * + * @return The activation constraints + */ + public String getActivationConstraints() { + return activationConstraints; + } + + /** + * Set the activation constraints + * + * @param _activationConstraints The activation constraints + */ + public void setActivationConstraints(String _activationConstraints) { + activationConstraints = _activationConstraints; + } + + /** + * Get the last time device was seen + * + * @return The last time device was seen + */ + public long getLastSeen() { + return lastSeen; + } + + /** + * Reset uplink frame counter + */ + public void resetFCntUp() { + fCntUp = 0; + } + + /** + * Reset downlink frame counter + */ + public void resetFCntDown() { + fCntDown = 0; + } + +} diff --git a/management/src/main/java/org/thethingsnetwork/management/async/AsyncDiscovery.java b/management/src/main/java/org/thethingsnetwork/management/async/AsyncDiscovery.java index f565e8d..c347894 100644 --- a/management/src/main/java/org/thethingsnetwork/management/async/AsyncDiscovery.java +++ b/management/src/main/java/org/thethingsnetwork/management/async/AsyncDiscovery.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,7 +26,7 @@ import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import java.io.ByteArrayInputStream; -import org.thethingsnetwork.account.auth.token.OAuth2Token; +import org.thethingsnetwork.account.async.auth.token.AsyncOAuth2Token; import org.thethingsnetwork.management.proto.DiscoveryGrpc; import org.thethingsnetwork.management.proto.DiscoveryOuterClass; import org.thethingsnetwork.management.proto.DiscoveryOuterClass.GetRequest; @@ -35,13 +35,20 @@ import rx.schedulers.Schedulers; /** + * This class is an async wrapper for the The Things Network discovery service * * @author Romain Cambier */ public class AsyncDiscovery { - private static final String HOST = "discovery.thethingsnetwork.org"; - private static final int PORT = 1900; + /** + * Main The Things Network discovery server host + */ + public static final String HOST = "discovery.thethingsnetwork.org"; + /** + * Main The Things Network discovery server port + */ + public static final int PORT = 1900; private final DiscoveryGrpc.DiscoveryFutureStub stub; @@ -49,6 +56,13 @@ private AsyncDiscovery(DiscoveryGrpc.DiscoveryFutureStub _stub) { stub = _stub; } + /** + * Build an AsyncDiscovery wrapper from Host and Port + * + * @param _host The server host + * @param _port The server port + * @return An Observable stream containing the newly built AsyncDiscovery wrapper + */ public static Observable from(String _host, int _port) { return Observable .create((Subscriber t) -> { @@ -66,24 +80,42 @@ public static Observable from(String _host, int _port) { }); } + /** + * Build an AsyncDiscovery wrapper using default servers + * + * @return An Observable stream containing the newly built AsyncDiscovery wrapper + */ public static Observable getDefault() { return from(HOST, PORT); } - public Observable getHandler(OAuth2Token _creds, String _handlerId) { + /** + * Fetch discovery service for the specified handler + * + * @param _creds A valid authentication token + * @param _handlerId The handler id + * @return An Observable stream containing the AsyncHandler wrapper + */ + public Observable getHandler(AsyncOAuth2Token _creds, String _handlerId) { return Observable .from(stub.get(GetRequest.newBuilder().setId(_handlerId).setServiceName(Services.HANDLER.name().toLowerCase()).build()), Schedulers.io()) .flatMap((DiscoveryOuterClass.Announcement t) -> from(_creds, t)); } - public Observable getHandlers(OAuth2Token _creds) { + /** + * Fetch discovery service for all handlers + * + * @param _creds A valid authentication token + * @return An Observable stream containing the AsyncHandler wrappers + */ + public Observable getHandlers(AsyncOAuth2Token _creds) { return Observable .from(stub.getAll(DiscoveryOuterClass.GetServiceRequest.newBuilder().setServiceName(Services.HANDLER.name().toLowerCase()).build()), Schedulers.io()) .flatMap((DiscoveryOuterClass.AnnouncementsResponse t) -> Observable.from(t.getServicesList())) .flatMap((DiscoveryOuterClass.Announcement t) -> from(_creds, t)); } - private Observable from(OAuth2Token _creds, DiscoveryOuterClass.Announcement _announcement) { + private Observable from(AsyncOAuth2Token _creds, DiscoveryOuterClass.Announcement _announcement) { return Observable.from(_announcement.getNetAddress().split(",")) .flatMap((String tt) -> Observable .create((Subscriber t) -> { @@ -114,9 +146,21 @@ public Server(String _s) { } + /** + * List of known The Things Network services + */ public static enum Services { + /** + * Handler Service. Responsible of device management + */ HANDLER, + /** + * Broker Service. Responsible of data deduplication and Handler routing + */ BROKER, + /** + * Router Service. Responsible of global routing + */ ROUTER } } diff --git a/management/src/main/java/org/thethingsnetwork/management/async/AsyncHandler.java b/management/src/main/java/org/thethingsnetwork/management/async/AsyncHandler.java index 76af16a..a73e151 100644 --- a/management/src/main/java/org/thethingsnetwork/management/async/AsyncHandler.java +++ b/management/src/main/java/org/thethingsnetwork/management/async/AsyncHandler.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -34,8 +34,8 @@ import io.grpc.netty.NegotiationType; import io.grpc.netty.NettyChannelBuilder; import java.io.InputStream; -import org.thethingsnetwork.account.AbstractApplication; -import org.thethingsnetwork.account.auth.token.OAuth2Token; +import org.thethingsnetwork.account.async.auth.token.AsyncOAuth2Token; +import org.thethingsnetwork.account.common.AbstractApplication; import org.thethingsnetwork.management.HandlerApplication; import org.thethingsnetwork.management.HandlerDevice; import org.thethingsnetwork.management.proto.ApplicationManagerGrpc; @@ -45,6 +45,7 @@ import rx.schedulers.Schedulers; /** + * This class is an async wrapper for the The Things Network handler service * * @author Romain Cambier */ @@ -56,7 +57,16 @@ private AsyncHandler(ApplicationManagerGrpc.ApplicationManagerFutureStub _stub) stub = _stub; } - public static Observable from(OAuth2Token _credentials, String _host, int _port, InputStream _certificate) { + /** + * Build an AsyncHandler instance + * + * @param _credentials A valid authentication token + * @param _host The handler host + * @param _port The handler port + * @param _certificate The handler certificate + * @return An Observable stream containing the newly built AsyncHandler wrapper + */ + public static Observable from(AsyncOAuth2Token _credentials, String _host, int _port, InputStream _certificate) { return Observable .create((Subscriber t) -> { @@ -98,6 +108,12 @@ public void start(ClientCall.Listener responseListener, Metadata headers) } + /** + * Register an application to The Things Network + * + * @param _application the application to register + * @return An Observable stream containing the newly registered HandlerApplication + */ public Observable registerApplication(AbstractApplication _application) { return Observable .from(stub.registerApplication( @@ -109,11 +125,23 @@ public Observable registerApplication(AbstractApplication _a .flatMap((ignore) -> getApplication(_application.getId())); } + /** + * Get an application from the handler service + * + * @param _applicationId The id of the application + * @return An Observable stream containing the requested HandlerApplication + */ public Observable getApplication(String _applicationId) { return Observable.from(stub.getApplication(HandlerOuterClass.ApplicationIdentifier.newBuilder().setAppId(_applicationId).build()), Schedulers.io()) .flatMap(HandlerApplication::from); } + /** + * Update (or create) an application on the handler service + * + * @param _application The application (new or updated) + * @return An Observable stream containing the updated HandlerApplication + */ public Observable setApplication(HandlerApplication _application) { return _application .toProto() @@ -121,6 +149,12 @@ public Observable setApplication(HandlerApplication _applica .map((ignore) -> _application); } + /** + * Delete an application from the handler service + * + * @param _application The application + * @return An Observable stream containing the deleted HandlerApplication + */ public Observable deleteApplication(HandlerApplication _application) { return Observable .from(stub.deleteApplication( @@ -132,20 +166,73 @@ public Observable deleteApplication(HandlerApplication _appl .map((ignore) -> _application); } + /** + * Get all devices from the handler service + * + * @param _application The application to list devices of + * @return An Observable stream containing the HandlerDevice objects + */ public Observable getDevices(HandlerApplication _application) { - return null; + return Observable + .from(stub.getDevicesForApplication( + HandlerOuterClass.ApplicationIdentifier + .newBuilder() + .setAppId(_application.getAppId()) + .build() + ), Schedulers.io()) + .flatMap((HandlerOuterClass.DeviceList t) -> Observable.from(t.getDevicesList())) + .flatMap((HandlerOuterClass.Device t) -> HandlerDevice.from(t)); } - public Observable getDevice(String _deviceId) { - return null; + /** + * Get a device from the handler service + * + * @param _application The application containing the device + * @param _deviceId The device id + * @return An Observable stream containing the HandlerDevice + */ + public Observable getDevice(HandlerApplication _application, String _deviceId) { + return Observable + .from(stub.getDevice( + HandlerOuterClass.DeviceIdentifier + .newBuilder() + .setAppId(_application.getAppId()) + .setDevId(_deviceId) + .build() + ), Schedulers.io()) + .flatMap((HandlerOuterClass.Device t) -> HandlerDevice.from(t)); } + /** + * Update (or create) a device on the handler service + * + * @param _device The device + * @return An Observable stream containing the updated HandlerDevice + */ public Observable setDevice(HandlerDevice _device) { - return null; + return _device.toProto() + .flatMap((HandlerOuterClass.Device tt) -> Observable + .from(stub.setDevice(tt), Schedulers.io()) + .map((t) -> _device)); + } + /** + * Delete a device on the handler service + * + * @param _device The device + * @return An Observable stream containing the deleted HandlerDevice + */ public Observable deleteDevice(HandlerDevice _device) { - return null; + return Observable + .from(stub.deleteDevice( + HandlerOuterClass.DeviceIdentifier + .newBuilder() + .setAppId(_device.getAppId()) + .setDevId(_device.getDevId()) + .build() + ), Schedulers.io()) + .map((t) -> _device); } } diff --git a/management/src/main/java/org/thethingsnetwork/management/sync/Discovery.java b/management/src/main/java/org/thethingsnetwork/management/sync/Discovery.java new file mode 100644 index 0000000..e51bc58 --- /dev/null +++ b/management/src/main/java/org/thethingsnetwork/management/sync/Discovery.java @@ -0,0 +1,93 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.management.sync; + +import java.util.List; +import org.thethingsnetwork.account.sync.auth.token.OAuth2Token; +import org.thethingsnetwork.management.async.AsyncDiscovery; +import org.thethingsnetwork.management.async.AsyncHandler; + +/** + * This class is a wrapper for the The Things Network discovery service + * @author Romain Cambier + */ +public class Discovery { + + private final AsyncDiscovery wrapped; + + protected Discovery(AsyncDiscovery _wrap) { + wrapped = _wrap; + } + + /** + * Build a Discovery wrapper from Host and Port + * + * @param _host The server host + * @param _port The server port + * @return The newly built Discovery wrapper + */ + public static Discovery from(String _host, int _port) { + return AsyncDiscovery.from(_host, _port) + .map((AsyncDiscovery t) -> new Discovery(t)) + .toBlocking() + .single(); + } + + /** + * Build a Discovery wrapper using default servers + * + * @return The newly built Discovery wrapper + */ + public static Discovery getDefault() { + return from(AsyncDiscovery.HOST, AsyncDiscovery.PORT); + } + + /** + * Fetch discovery service for the specified handler + * + * @param _creds A valid authentication token + * @param _handlerId The handler id + * @return The requested Handler + */ + public Handler getHandler(OAuth2Token _creds, String _handlerId) { + return wrapped.getHandler(_creds.async(), _handlerId) + .map((AsyncHandler t) -> new Handler(t)) + .toBlocking() + .single(); + } + + /** + * Fetch discovery service for all handlers + * + * @param _creds A valid authentication token + * @return A list of the Handler wrappers + */ + public List getHandlers(OAuth2Token _creds) { + return wrapped.getHandlers(_creds.async()) + .map((AsyncHandler t) -> new Handler(t)) + .toList() + .toBlocking() + .single(); + } +} diff --git a/management/src/main/java/org/thethingsnetwork/management/sync/Handler.java b/management/src/main/java/org/thethingsnetwork/management/sync/Handler.java new file mode 100644 index 0000000..016d534 --- /dev/null +++ b/management/src/main/java/org/thethingsnetwork/management/sync/Handler.java @@ -0,0 +1,162 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.management.sync; + +import java.io.InputStream; +import java.util.List; +import org.thethingsnetwork.account.common.AbstractApplication; +import org.thethingsnetwork.account.sync.auth.token.OAuth2Token; +import org.thethingsnetwork.management.HandlerApplication; +import org.thethingsnetwork.management.HandlerDevice; +import org.thethingsnetwork.management.async.AsyncHandler; + +/** + * This class is a wrapper for the The Things Network handler service + * + * @author Romain Cambier + */ +public class Handler { + + private final AsyncHandler wrapped; + + protected Handler(AsyncHandler _wrap) { + wrapped = _wrap; + } + + /** + * Build a Handler wrapper instance + * + * @param _credentials A valid authentication token + * @param _host The handler host + * @param _port The handler port + * @param _certificate The handler certificate + * @return The newly built Handler + */ + public static Handler from(OAuth2Token _credentials, String _host, int _port, InputStream _certificate) { + return AsyncHandler.from(_credentials.async(), _host, _port, _certificate) + .map((AsyncHandler t) -> new Handler(t)) + .toBlocking() + .single(); + } + + /** + * Register an application to The Things Network + * + * @param _application the application to register + * @return The newly registered HandlerApplication + */ + public HandlerApplication registerApplication(AbstractApplication _application) { + return wrapped.registerApplication(_application) + .toBlocking() + .single(); + } + + /** + * Get an application from the handler service + * + * @param _applicationId The id of the application + * @return The requested HandlerApplication + */ + public HandlerApplication getApplication(String _applicationId) { + return wrapped.getApplication(_applicationId) + .toBlocking() + .single(); + } + + /** + * Update (or create) an application on the handler service + * + * @param _application The application (new or updated) + * @return The updated HandlerApplication + */ + public HandlerApplication setApplication(HandlerApplication _application) { + return wrapped.setApplication(_application) + .toBlocking() + .single(); + } + + /** + * Delete an application from the handler service + * + * @param _application The application + * @return The deleted HandlerApplication + */ + public HandlerApplication deleteApplication(HandlerApplication _application) { + return wrapped.deleteApplication(_application) + .toBlocking() + .single(); + } + + /** + * Get all devices from the handler service + * + * @param _application The application to list devices of + * @return The List of HandlerDevice objects + */ + public List getDevices(HandlerApplication _application) { + return wrapped.getDevices(_application) + .toList() + .toBlocking() + .single(); + } + + /** + * Get a device from the handler service + * + * @param _application The application containing the device + * @param _deviceId The device id + * @return The HandlerDevice object + */ + public HandlerDevice getDevice(HandlerApplication _application, String _deviceId) { + return wrapped.getDevice(_application, _deviceId) + .toBlocking() + .single(); + } + + /** + * Update (or create) a device on the handler service + * + * @param _device The device + * @return The updated HandlerDevice + */ + public HandlerDevice setDevice(HandlerDevice _device) { + return wrapped.setDevice(_device) + .toBlocking() + .single(); + + } + + /** + * Delete a device on the handler service + * + * @param _device The device + * @return The deleted HandlerDevice + */ + public HandlerDevice deleteDevice(HandlerDevice _device) { + return wrapped.deleteDevice(_device) + .toBlocking() + .single(); + } + +} diff --git a/pom.xml b/pom.xml index c9890f1..27626e1 100644 --- a/pom.xml +++ b/pom.xml @@ -3,13 +3,13 @@ 4.0.0 org.thethingsnetwork app-sdk - 2.0.0 + 2.1.0 pom - account data management + account The Things Network Java SDK diff --git a/samples/README.md b/samples/README.md index 2a472d3..9e42625 100644 --- a/samples/README.md +++ b/samples/README.md @@ -1 +1,14 @@ -# The Things Network Java SDK Samples \ No newline at end of file +# The Things Network Java SDK Samples + +Here are some samples codes on how to start with the Java SDK + +## Saples + +- [Account](samples-account) - Interact with The Things Network account server +- [Data AMQP](samples-amqp) - Subscribe to Things Network Handler to send/receive data via AMQP +- [Data MQTT](samples-mqtt) - Subscribe to Things Network Handler to send/receive data via MQTT + +## Documentation + +- [The Things Network Documentation](https://www.thethingsnetwork.org/docs/applications/java/) +- [Javadoc](https://thethingsnetwork.github.io/java-app-sdk/) diff --git a/samples/pom.xml b/samples/pom.xml index eff15a7..6517590 100644 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -3,13 +3,16 @@ 4.0.0 org.thethingsnetwork samples - 2.0.0 + 2.1.0 pom The Things Network SDK Samples The Things Network SDK Samples - mqtt + samples-mqtt + samples-account + samples-amqp + samples-management UTF-8 diff --git a/samples/samples-account/pom.xml b/samples/samples-account/pom.xml new file mode 100644 index 0000000..d0756ec --- /dev/null +++ b/samples/samples-account/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + org.thethingsnetwork + samples + 2.1.0 + + samples-account + jar + + The Things Network Account Sample + The Things Network Account Sample + + + ${project.groupId} + account + ${project.version} + + + + UTF-8 + 1.8 + 1.8 + + \ No newline at end of file diff --git a/samples/samples-account/src/main/java/org/thethingsnetwork/samples/account/App.java b/samples/samples-account/src/main/java/org/thethingsnetwork/samples/account/App.java new file mode 100644 index 0000000..832efd3 --- /dev/null +++ b/samples/samples-account/src/main/java/org/thethingsnetwork/samples/account/App.java @@ -0,0 +1,67 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.samples.account; + +import java.net.URI; +import org.thethingsnetwork.account.util.HttpRequest; + +/** + * + * @author Romain Cambier + */ +public class App { + + public static void main(String[] args) throws Exception { + Config cfg = new Config(); + cfg.cliendId = System.getenv("cliendId"); + cfg.clientSecret = System.getenv("clientSecret"); + cfg.redirect = (System.getenv("redirect") == null) ? null : new URI(System.getenv("redirect")); + cfg.applicationId = System.getenv("applicationId"); + cfg.applicationKey = System.getenv("applicationKey"); + + System.out.println("Starting AuthorizationCodeAsync"); + AuthorizationCodeAsync.run(cfg); + System.out.println("Starting AuthorizationCodeSync"); + AuthorizationCodeSync.run(cfg); + System.out.println("Starting ApplicationPasswordAsync"); + ApplicationPasswordAsync.run(cfg); + System.out.println("Starting ApplicationPasswordSync"); + ApplicationPasswordSync.run(cfg); + + HttpRequest.shutdown(); + + } + + public static class Config { + + public String cliendId; + public String clientSecret; + //for authorization code + public URI redirect; + //for app password + public String applicationId; + public String applicationKey; + + } +} diff --git a/samples/samples-account/src/main/java/org/thethingsnetwork/samples/account/ApplicationPasswordAsync.java b/samples/samples-account/src/main/java/org/thethingsnetwork/samples/account/ApplicationPasswordAsync.java new file mode 100644 index 0000000..e62c6f8 --- /dev/null +++ b/samples/samples-account/src/main/java/org/thethingsnetwork/samples/account/ApplicationPasswordAsync.java @@ -0,0 +1,129 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.samples.account; + +import java.util.concurrent.CountDownLatch; +import org.thethingsnetwork.account.async.AsyncApplication; +import org.thethingsnetwork.account.async.auth.grant.AsyncApplicationPassword; +import org.thethingsnetwork.account.async.auth.token.AsyncJsonWebToken; +import org.thethingsnetwork.samples.account.App.Config; +import rx.Observable; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Func1; + +/** + * + * @author Romain Cambier + */ +public class ApplicationPasswordAsync { + + public static void run(Config _conf) throws Exception { + if (_conf.cliendId == null) { + throw new NullPointerException("missing cliendId"); + } + + if (_conf.clientSecret == null) { + throw new NullPointerException("missing clientSecret"); + } + + if (_conf.applicationId == null) { + throw new NullPointerException("missing applicationId"); + } + + if (_conf.applicationKey == null) { + throw new NullPointerException("missing applicationKey"); + } + + AsyncApplicationPassword tokenProvider = new AsyncApplicationPassword(_conf.applicationId, _conf.applicationKey, _conf.cliendId, _conf.clientSecret); + + /** + * I won't use lambdas just so that everybody can ready this code ! + */ + CountDownLatch cdl = new CountDownLatch(1); + tokenProvider + .getToken() + .flatMap(new Func1>() { + @Override + public Observable call(AsyncJsonWebToken t) { + return AsyncApplication.findOne(t, _conf.applicationId); + } + }) + .flatMap(new Func1>() { + @Override + public Observable call(AsyncApplication app) { + return app + .getAccessKeys() + .count() + .map(new Func1() { + @Override + public String call(Integer count) { + return "\tapplication " + app.getName() + " has " + count + " keys"; + } + }); + } + }) + .doOnNext(new Action1() { + @Override + public void call(String t) { + System.out.println(t); + } + }) + .doOnCompleted(new Action0() { + @Override + public void call() { + cdl.countDown(); + } + }) + .doOnError(new Action1() { + @Override + public void call(Throwable t) { + t.printStackTrace(); + } + }) + .subscribe(); + /** + * this is to prevent exiting the run() before the async code complete. + * In a real async flow, you don't block stuff ! + */ + cdl.await(); + + /** + * Just so that you know how lambdas looks: + */ + tokenProvider + .getToken() + .flatMap((AsyncJsonWebToken t) -> AsyncApplication.findOne(t, _conf.applicationId)) + .flatMap((AsyncApplication app) -> app + .getAccessKeys() + .count() + .map((Integer count) -> "\tapplication " + app.getName() + " has " + count + " keys") + ) + .doOnNext(System.out::println) + .doOnCompleted(cdl::countDown) + .doOnError(Throwable::printStackTrace); //I removed the subscribe() to avoid running the code twice + + } + +} diff --git a/samples/samples-account/src/main/java/org/thethingsnetwork/samples/account/ApplicationPasswordSync.java b/samples/samples-account/src/main/java/org/thethingsnetwork/samples/account/ApplicationPasswordSync.java new file mode 100644 index 0000000..0540e67 --- /dev/null +++ b/samples/samples-account/src/main/java/org/thethingsnetwork/samples/account/ApplicationPasswordSync.java @@ -0,0 +1,66 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.samples.account; + +import java.util.List; +import org.thethingsnetwork.account.common.AccessKey; +import org.thethingsnetwork.account.sync.Application; +import org.thethingsnetwork.account.sync.auth.grant.ApplicationPassword; +import org.thethingsnetwork.account.sync.auth.token.JsonWebToken; + +/** + * + * @author Romain Cambier + */ +public class ApplicationPasswordSync { + + public static void run(App.Config _conf) { + if (_conf.cliendId == null) { + throw new NullPointerException("missing cliendId"); + } + + if (_conf.clientSecret == null) { + throw new NullPointerException("missing clientSecret"); + } + + if (_conf.applicationId == null) { + throw new NullPointerException("missing applicationId"); + } + + if (_conf.applicationKey == null) { + throw new NullPointerException("missing applicationKey"); + } + + ApplicationPassword tokenProvider = new ApplicationPassword(_conf.applicationId, _conf.applicationKey, _conf.cliendId, _conf.clientSecret); + + JsonWebToken token = tokenProvider.getToken(); + + Application app = Application.findOne(token, _conf.applicationId); + + List accessKeys = app.getAccessKeys(); + + System.out.println("\tapplication " + app.getName() + " has " + accessKeys.size() + " keys"); + } + +} diff --git a/samples/samples-account/src/main/java/org/thethingsnetwork/samples/account/AuthorizationCodeAsync.java b/samples/samples-account/src/main/java/org/thethingsnetwork/samples/account/AuthorizationCodeAsync.java new file mode 100644 index 0000000..0c23a91 --- /dev/null +++ b/samples/samples-account/src/main/java/org/thethingsnetwork/samples/account/AuthorizationCodeAsync.java @@ -0,0 +1,156 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.samples.account; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.concurrent.CountDownLatch; +import org.thethingsnetwork.account.async.AsyncApplication; +import org.thethingsnetwork.account.async.auth.grant.AsyncAuthorizationCode; +import org.thethingsnetwork.account.async.auth.token.AsyncRenewableJsonWebToken; +import org.thethingsnetwork.account.common.AccessKey; +import rx.Observable; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Func1; + +/** + * + * @author Romain Cambier + */ +public class AuthorizationCodeAsync { + + public static void run(App.Config _conf) throws Exception { + + if (_conf.cliendId == null) { + throw new NullPointerException("missing cliendId"); + } + + if (_conf.clientSecret == null) { + throw new NullPointerException("missing clientSecret"); + } + + if (_conf.redirect == null) { + throw new NullPointerException("missing redirect"); + } + + AsyncAuthorizationCode tokenProvider = new AsyncAuthorizationCode(_conf.cliendId, _conf.clientSecret); + + String redirect = tokenProvider.buildAuthorizationURL(_conf.redirect).toString(); + + System.out.println("here is the authorization url: " + redirect); + System.out.print("Enter code: "); + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + String code = br.readLine(); + + if (code == null || code.equals("")) { + throw new IllegalArgumentException("invalid code"); + } + + /** + * I won't use lambdas just so that everybody can ready this code ! + */ + CountDownLatch cdl = new CountDownLatch(1); + tokenProvider + .getToken(code) + .flatMap(new Func1>() { + @Override + public Observable call(AsyncRenewableJsonWebToken token) { + return AsyncApplication + .findAll(token) + .flatMap(new Func1>() { + @Override + public Observable call(AsyncApplication application) { + return token + .restrict("apps:" + application.getId()) + .doOnNext(new Action1() { + @Override + public void call(AsyncRenewableJsonWebToken restrictedToken) { + application.updateCredentials(restrictedToken); + } + }) + .flatMap(new Func1>() { + @Override + public Observable call(AsyncRenewableJsonWebToken restrictedToken) { + return application.getAccessKeys(); + } + }) + .count() + .map(new Func1() { + @Override + public String call(Integer accessKeysCount) { + return "\tapplication " + application.getName() + " has " + accessKeysCount + " keys"; + } + }); + } + }); + } + }) + .doOnNext(new Action1() { + @Override + public void call(String t) { + System.out.println(t); + } + }) + .doOnCompleted(new Action0() { + @Override + public void call() { + cdl.countDown(); + } + }) + .doOnError(new Action1() { + @Override + public void call(Throwable t) { + t.printStackTrace(); + } + }) + .subscribe(); + /** + * this is to prevent exiting the run() before the async code complete. + * In a real async flow, you don't block stuff ! + */ + cdl.await(); + + /** + * Just so that you know how lambdas looks: + */ + tokenProvider + .getToken(code) + .flatMap((AsyncRenewableJsonWebToken token) -> AsyncApplication + .findAll(token) + .flatMap((AsyncApplication application) -> token + .restrict("apps:" + application.getId()) + .doOnNext(application::updateCredentials) + .flatMap((ignore) -> application.getAccessKeys()) + .count() + .map((Integer accessKeysCount) -> "\tapplication " + application.getName() + " has " + accessKeysCount + " keys") + ) + ) + .doOnNext(System.out::println) + .doOnCompleted(cdl::countDown) + .doOnError(Throwable::printStackTrace); //I removed the subscribe() to avoid running the code twice + + } + +} diff --git a/samples/samples-account/src/main/java/org/thethingsnetwork/samples/account/AuthorizationCodeSync.java b/samples/samples-account/src/main/java/org/thethingsnetwork/samples/account/AuthorizationCodeSync.java new file mode 100644 index 0000000..a4db894 --- /dev/null +++ b/samples/samples-account/src/main/java/org/thethingsnetwork/samples/account/AuthorizationCodeSync.java @@ -0,0 +1,83 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.samples.account; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.List; +import org.thethingsnetwork.account.common.AccessKey; +import org.thethingsnetwork.account.sync.Application; +import org.thethingsnetwork.account.sync.auth.grant.AuthorizationCode; +import org.thethingsnetwork.account.sync.auth.token.RenewableJsonWebToken; + +/** + * + * @author Romain Cambier + */ +public class AuthorizationCodeSync { + + public static void run(App.Config _conf) throws Exception { + if (_conf.cliendId == null) { + throw new NullPointerException("missing cliendId"); + } + + if (_conf.clientSecret == null) { + throw new NullPointerException("missing clientSecret"); + } + + if (_conf.redirect == null) { + throw new NullPointerException("missing redirect"); + } + + AuthorizationCode tokenProvider = new AuthorizationCode(_conf.cliendId, _conf.clientSecret); + + String redirect = tokenProvider.buildAuthorizationURL(_conf.redirect).toString(); + + System.out.println("here is the authorization url: " + redirect); + System.out.print("Enter code: "); + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + String code = br.readLine(); + + if (code == null || code.equals("")) { + throw new IllegalArgumentException("invalid code"); + } + + RenewableJsonWebToken token = tokenProvider.getToken(code); + + List apps = Application.findAll(token); + + for (Application app : apps) { + + RenewableJsonWebToken restrictedToken = token.restrict("apps:" + app.getId()); + + app.updateCredentials(restrictedToken); + + List accessKeys = app.getAccessKeys(); + + System.out.println("\tapplication " + app.getName() + " has " + accessKeys.size() + " keys"); + + } + } + +} diff --git a/samples/samples-amqp/pom.xml b/samples/samples-amqp/pom.xml new file mode 100644 index 0000000..84ea2d7 --- /dev/null +++ b/samples/samples-amqp/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + org.thethingsnetwork + samples + 2.1.0 + + samples-amqp + jar + + The Things Network AMQP Sample + The Things Network AMQP Sample + + + ${project.groupId} + data-amqp + ${project.version} + + + + UTF-8 + 1.8 + 1.8 + + \ No newline at end of file diff --git a/samples/samples-amqp/src/main/java/org/thethingsnetwork/samples/amqp/App.java b/samples/samples-amqp/src/main/java/org/thethingsnetwork/samples/amqp/App.java new file mode 100644 index 0000000..1b4547b --- /dev/null +++ b/samples/samples-amqp/src/main/java/org/thethingsnetwork/samples/amqp/App.java @@ -0,0 +1,84 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.samples.mqtt; + +import org.thethingsnetwork.data.amqp.Client; +import org.thethingsnetwork.data.common.Connection; +import org.thethingsnetwork.data.common.messages.ActivationMessage; +import org.thethingsnetwork.data.common.messages.DataMessage; +import org.thethingsnetwork.data.common.messages.DownlinkMessage; +import org.thethingsnetwork.data.common.messages.RawMessage; +import org.thethingsnetwork.data.common.messages.UplinkMessage; + +/** + * + * @author Romain Cambier + */ +public class App { + + public static void main(String[] args) throws Exception { + String region = System.getenv("region"); + String appId = System.getenv("appId"); + String accessKey = System.getenv("accessKey"); + + Client client = new Client(region, appId, accessKey); + + class Response { + + private boolean led; + + public Response(boolean _led) { + led = _led; + } + } + + client.onMessage(null, "led", (String _devId, DataMessage _data) -> { + try { + RawMessage message = (RawMessage) _data; + // Toggle the LED + DownlinkMessage response = new DownlinkMessage(0, new Response(!message.asBoolean())); + + /** + * If you don't have an encoder payload function: + * client.send(_devId, new Response(0, message.asBoolean() ? new byte[]{0x00} : new byte[]{0x01})); + */ + System.out.println("Sending: " + response); + client.send(_devId, response); + } catch (Exception ex) { + System.out.println("Response failed: " + ex.getMessage()); + } + }); + + client.onMessage((String devId, DataMessage data) -> System.out.println("Message: " + devId + " " + ((UplinkMessage) data).getCounter())); + + client.onActivation((String _devId, ActivationMessage _data) -> System.out.println("Activation: " + _devId + ", data: " + _data.getDevAddr())); + + client.onError((Throwable _error) -> System.err.println("error: " + _error.getMessage())); + + client.onConnected((Connection _client) -> System.out.println("connected !")); + + client.start(); + } + +} diff --git a/samples/samples-management/pom.xml b/samples/samples-management/pom.xml new file mode 100644 index 0000000..2928aa1 --- /dev/null +++ b/samples/samples-management/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + org.thethingsnetwork + samples + 2.1.0 + + samples-management + jar + + The Things Network Management Sample + The Things Network Management Sample + + + ${project.groupId} + management + ${project.version} + + + + UTF-8 + 1.8 + 1.8 + + \ No newline at end of file diff --git a/samples/samples-management/src/main/java/org/thethingsnetwork/samples/management/App.java b/samples/samples-management/src/main/java/org/thethingsnetwork/samples/management/App.java new file mode 100644 index 0000000..0a116e5 --- /dev/null +++ b/samples/samples-management/src/main/java/org/thethingsnetwork/samples/management/App.java @@ -0,0 +1,74 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.samples.management; + +import org.thethingsnetwork.account.util.HttpRequest; + +/** + * + * @author Romain Cambier + */ +public class App { + + public static void main(String[] args) throws Exception { + Config cfg = new Config(); + cfg.cliendId = System.getenv("cliendId"); + cfg.clientSecret = System.getenv("clientSecret"); + cfg.handlerId = System.getenv("handlerId"); + cfg.applicationId = System.getenv("applicationId"); + cfg.applicationKey = System.getenv("applicationKey"); + + System.out.println("Starting ManagementAsync"); + ManagementAsync.run(cfg); + System.out.println("Starting ManagementSync"); + ManagementSync.run(cfg); + + HttpRequest.shutdown(); + + } + + public static class Config { + + public String cliendId; + public String clientSecret; + //for app password + public String applicationId; + public String applicationKey; + //for handler choice + public String handlerId; + + private final static char[] hexChars = "0123456789ABCDEF".toCharArray(); + + public String printArray(byte[] _data) { + char[] hexChars = new char[2 * _data.length]; + for (int i = 0; i < _data.length; i++) { + int j = _data[i] & 0xFF; + hexChars[i * 2] = hexChars[j >>> 4]; + hexChars[i * 2 + 1] = hexChars[j & 0x0F]; + } + return new String(hexChars); + } + } + +} diff --git a/samples/samples-management/src/main/java/org/thethingsnetwork/samples/management/ManagementAsync.java b/samples/samples-management/src/main/java/org/thethingsnetwork/samples/management/ManagementAsync.java new file mode 100644 index 0000000..50e3238 --- /dev/null +++ b/samples/samples-management/src/main/java/org/thethingsnetwork/samples/management/ManagementAsync.java @@ -0,0 +1,92 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.samples.management; + +import java.util.concurrent.CountDownLatch; +import org.thethingsnetwork.account.async.auth.grant.AsyncApplicationPassword; +import org.thethingsnetwork.account.async.auth.token.AsyncJsonWebToken; +import org.thethingsnetwork.management.HandlerApplication; +import org.thethingsnetwork.management.HandlerDevice; +import org.thethingsnetwork.management.async.AsyncDiscovery; +import org.thethingsnetwork.management.async.AsyncHandler; + +/** + * + * @author Romain Cambier + */ +public class ManagementAsync { + + public static void run(App.Config _conf) throws Exception { + + if (_conf.cliendId == null) { + throw new NullPointerException("missing cliendId"); + } + + if (_conf.clientSecret == null) { + throw new NullPointerException("missing clientSecret"); + } + + if (_conf.applicationId == null) { + throw new NullPointerException("missing applicationId"); + } + + if (_conf.applicationKey == null) { + throw new NullPointerException("missing applicationKey"); + } + + if (_conf.handlerId == null) { + throw new NullPointerException("missing handlerId"); + } + + AsyncApplicationPassword tokenProvider = new AsyncApplicationPassword(_conf.applicationId, _conf.applicationKey, _conf.cliendId, _conf.clientSecret); + + CountDownLatch cdl = new CountDownLatch(1); + + tokenProvider + .getToken() + .flatMap((AsyncJsonWebToken token) -> AsyncDiscovery + .getDefault() + .flatMap((AsyncDiscovery t) -> t.getHandler(token, _conf.handlerId)) + ) + .single() + .flatMap((AsyncHandler handler) -> handler + .getApplication(_conf.applicationId) + .doOnNext((HandlerApplication app) -> { + System.out.println("## HandlerApplication " + app.getAppId() + " has a decoder function being: \n" + app.getDecoder()); + System.out.println("## HandlerApplication " + app.getAppId() + " device list:"); + }) + .flatMap(handler::getDevices) + ) + .doOnNext((HandlerDevice t) -> { + System.out.println("\t HandlerDevice " + t.getDevId() + " has EUI " + _conf.printArray(t.getLorawan().getDevEui())); + }) + .doOnCompleted(cdl::countDown) + .doOnError(Throwable::printStackTrace) + .subscribe(); + + cdl.await(); + + } + +} diff --git a/samples/samples-management/src/main/java/org/thethingsnetwork/samples/management/ManagementSync.java b/samples/samples-management/src/main/java/org/thethingsnetwork/samples/management/ManagementSync.java new file mode 100644 index 0000000..271b80d --- /dev/null +++ b/samples/samples-management/src/main/java/org/thethingsnetwork/samples/management/ManagementSync.java @@ -0,0 +1,83 @@ +/* + * The MIT License + * + * Copyright (c) 2017 The Things Network + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.thethingsnetwork.samples.management; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import org.thethingsnetwork.account.sync.auth.grant.ApplicationPassword; +import org.thethingsnetwork.account.sync.auth.token.JsonWebToken; +import org.thethingsnetwork.management.HandlerApplication; +import org.thethingsnetwork.management.HandlerDevice; +import org.thethingsnetwork.management.sync.Discovery; +import org.thethingsnetwork.management.sync.Handler; + +/** + * + * @author Romain Cambier + */ +public class ManagementSync { + + public static void run(App.Config _conf) { + if (_conf.cliendId == null) { + throw new NullPointerException("missing cliendId"); + } + + if (_conf.clientSecret == null) { + throw new NullPointerException("missing clientSecret"); + } + + if (_conf.applicationId == null) { + throw new NullPointerException("missing applicationId"); + } + + if (_conf.applicationKey == null) { + throw new NullPointerException("missing applicationKey"); + } + + if (_conf.handlerId == null) { + throw new NullPointerException("missing handlerId"); + } + + ApplicationPassword tokenProvider = new ApplicationPassword(_conf.applicationId, _conf.applicationKey, _conf.cliendId, _conf.clientSecret); + + CountDownLatch cdl = new CountDownLatch(1); + + JsonWebToken token = tokenProvider.getToken(); + + Discovery discoveryServer = Discovery.getDefault(); + + Handler handler = discoveryServer.getHandler(token, _conf.handlerId); + + HandlerApplication application = handler.getApplication(_conf.applicationId); + + System.out.println("## HandlerApplication " + application.getAppId() + " has a decoder function being: \n" + application.getDecoder()); + System.out.println("## HandlerApplication " + application.getAppId() + " device list:"); + + List devices = handler.getDevices(application); + + for(HandlerDevice device:devices){ + System.out.println("\t HandlerDevice " + device.getDevId() + " has EUI " + _conf.printArray(device.getLorawan().getDevEui())); + } + } +} diff --git a/samples/mqtt/pom.xml b/samples/samples-mqtt/pom.xml similarity index 96% rename from samples/mqtt/pom.xml rename to samples/samples-mqtt/pom.xml index 10ddc8b..23cb761 100644 --- a/samples/mqtt/pom.xml +++ b/samples/samples-mqtt/pom.xml @@ -4,7 +4,7 @@ org.thethingsnetwork samples - 2.0.0 + 2.1.0 samples-mqtt jar diff --git a/samples/mqtt/src/main/java/org/thethingsnetwork/samples/mqtt/App.java b/samples/samples-mqtt/src/main/java/org/thethingsnetwork/samples/mqtt/App.java similarity index 63% rename from samples/mqtt/src/main/java/org/thethingsnetwork/samples/mqtt/App.java rename to samples/samples-mqtt/src/main/java/org/thethingsnetwork/samples/mqtt/App.java index 18b66b3..55285e6 100644 --- a/samples/mqtt/src/main/java/org/thethingsnetwork/samples/mqtt/App.java +++ b/samples/samples-mqtt/src/main/java/org/thethingsnetwork/samples/mqtt/App.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2016 The Things Network + * Copyright (c) 2017 The Things Network * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,8 +23,12 @@ */ package org.thethingsnetwork.samples.mqtt; -import org.json.JSONObject; import org.thethingsnetwork.data.common.Connection; +import org.thethingsnetwork.data.common.messages.ActivationMessage; +import org.thethingsnetwork.data.common.messages.DataMessage; +import org.thethingsnetwork.data.common.messages.DownlinkMessage; +import org.thethingsnetwork.data.common.messages.RawMessage; +import org.thethingsnetwork.data.common.messages.UplinkMessage; import org.thethingsnetwork.data.mqtt.Client; /** @@ -40,25 +44,35 @@ public static void main(String[] args) throws Exception { Client client = new Client(region, appId, accessKey); - client.onMessage(null, "led", (String _devId, Object _data) -> { + class Response { + + private boolean led; + + public Response(boolean _led) { + led = _led; + } + } + + client.onMessage(null, "led", (String _devId, DataMessage _data) -> { try { + RawMessage message = (RawMessage) _data; // Toggle the LED - JSONObject response = new JSONObject().put("led", !_data.equals("true")); + DownlinkMessage response = new DownlinkMessage(0, new Response(!message.asBoolean())); /** * If you don't have an encoder payload function: - * client.send(_devId, _data.equals("true") ? new byte[]{0x00} : new byte[]{0x01}, 0); + * client.send(_devId, new Response(0, message.asBoolean() ? new byte[]{0x00} : new byte[]{0x01})); */ System.out.println("Sending: " + response); - client.send(_devId, response, 0); + client.send(_devId, response); } catch (Exception ex) { System.out.println("Response failed: " + ex.getMessage()); } }); - client.onMessage((String devId, Object data) -> System.out.println("Message: " + devId + " " + data)); + client.onMessage((String devId, DataMessage data) -> System.out.println("Message: " + devId + " " + ((UplinkMessage) data).getCounter())); - client.onActivation((String _devId, JSONObject _data) -> System.out.println("Activation: " + _devId + ", data: " + _data)); + client.onActivation((String _devId, ActivationMessage _data) -> System.out.println("Activation: " + _devId + ", data: " + _data.getDevAddr())); client.onError((Throwable _error) -> System.err.println("error: " + _error.getMessage()));