Java tutorial
/* * Copyright (c) 2017, CESAR. * All rights reserved. * * This software may be modified and distributed under the terms * of the BSD license. See the LICENSE file for details. * * */ package br.org.cesar.knot.lib.connection; import android.os.Handler; import android.os.Looper; import android.text.TextUtils; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonParser; import com.squareup.okhttp.MediaType; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import com.squareup.okhttp.RequestBody; import com.squareup.okhttp.Response; import java.net.HttpURLConnection; import java.util.List; import br.org.cesar.knot.lib.event.Event; import br.org.cesar.knot.lib.exception.InvalidDeviceOwnerStateException; import br.org.cesar.knot.lib.exception.KnotException; import br.org.cesar.knot.lib.model.AbstractDeviceOwner; import br.org.cesar.knot.lib.model.AbstractThingData; import br.org.cesar.knot.lib.model.AbstractThingDevice; import br.org.cesar.knot.lib.model.AbstractThingMessage; import br.org.cesar.knot.lib.model.KnotList; import br.org.cesar.knot.lib.model.KnotQueryData; import br.org.cesar.knot.lib.util.DateUtils; /** * The main class that list all method available to use with KNOT */ final class ThingApi { public static final String EMPTY_ARRAY = "[]"; private static final String EMPTY_JSON = "{}"; private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); private static final String HEADER_AUTH_UUID = "meshblu_auth_uuid"; private static final String HEADER_AUTH_TOKEN = "meshblu_auth_token"; private static final String DATA_PATH = "/data/"; private static final String DEVICE_PATH = "/devices/"; private static final String DEVICE_PROPERTY_PATH_GATEWAY = "/gateway/"; private static final String CLAIM_DEVICES_PATH = "/claimdevice/"; private static final String WHOAMI = "/v2/whoami/"; private static final String MY_DEVICES_PATH = "/mydevices/"; private static final String MESSAGE = "/messages/"; private static final String JSON_DEVICES = "devices"; private static final String JSON_DATA = "data"; /** * Tag used to build the date query */ private static String DATE_START = "start"; /** * Tag used to build the date query */ private static String DATE_FINISH = "finish"; /** * Tag used to build the date query */ private static String LIMIT = "limit"; private static String EQUAL = "="; private static ThingApi sInstance; private final Handler mMainHandler; private final OkHttpClient mHttpClient; private final Gson mGson; private final String mEndPoint; private AbstractDeviceOwner abstractDeviceOwner; public ThingApi(String endPoint, String owner_uuid, String owner_token) { mMainHandler = new Handler(Looper.getMainLooper()); mHttpClient = new OkHttpClient(); mGson = new Gson(); mEndPoint = endPoint; abstractDeviceOwner = new AbstractDeviceOwner(); abstractDeviceOwner.setUuid(owner_uuid); abstractDeviceOwner.setToken(owner_token); } /** * Release the current reference of DeviceOwner */ public void releaseDeviceOwner() { this.abstractDeviceOwner = null; } /** * Generate a new Device in Meshblu instance * * @param device model sample to create a new one. Basically this device model * contains attributes that will be saved into Meshblu. * Please note that uuid and token will always * be generated by Meshblu (please see AbstractThingDevice). * It is important set the custom attribute for your classes * @return New device with meshblu token and uuid values * @throws KnotException KnotException * @see AbstractThingDevice */ public <T extends AbstractThingDevice> T createDevice(T device) throws KnotException { final String endPoint = mEndPoint + DEVICE_PATH; device.owner = abstractDeviceOwner.getUuid(); String json = mGson.toJson(device); RequestBody body = createRequestBodyWith(json); Request request = generateBasicRequestBuild(endPoint).post(body).build(); try { Response response = mHttpClient.newCall(request).execute(); // Retrieve the result of web service final T responseData = (T) mGson.fromJson(response.body().string(), device.getClass()); //Return the data sent by web server return responseData; } catch (Exception e) { throw new KnotException(e); } } /** * Async version of {@link #createDevice(AbstractThingDevice)} * * @param device model sample to create a new one. Basically this device model * contains attributes that will be saved into Meshblu. * Please note that uuid and token will always * be generated by Meshblu (please see AbstractThingDevice). * It is important set the custom attribute for your classes * @param callback Callback for this method * @see AbstractThingDevice */ public <T extends AbstractThingDevice> void createDevice(final T device, final Event<T> callback) { new Thread() { @Override public void run() { try { T result = createDevice(device); dispatchSuccess(callback, result); } catch (final KnotException e) { dispatchError(callback, e); } } }.start(); } /** * Turns the device belongs to someone. When a device is created in * Meshblu, it is an orphan device. In other words, everyone can made any * changes on this device. After claim a device, only the * owner can delete or update it. * Note: In Meshblu, the owner for one device IS another device. * * @param device the identifier of device (uuid) * @return a boolean value to indicate if the device could be claimed * @throws KnotException KnotException */ public Boolean claimDevice(String device) throws InvalidDeviceOwnerStateException, KnotException { if (!isValidDeviceOwner()) { throw new InvalidDeviceOwnerStateException("the device owner is null or invalid"); } // Create a request to claim the given device final String endPoint = mEndPoint + CLAIM_DEVICES_PATH + device; RequestBody body = createEmptyRequestBody(); Request request = generateBasicRequestBuild(this.abstractDeviceOwner.getUuid(), this.abstractDeviceOwner.getToken(), endPoint).put(body).build(); try { Response response = mHttpClient.newCall(request).execute(); return response.code() == HttpURLConnection.HTTP_OK; } catch (Exception e) { throw new KnotException(e); } } /** * Async version of {@link #claimDevice(String)} * * @param device the identifier of device (uuid) * @param callback Callback for this method */ public void claimDevice(final String device, final Event<Boolean> callback) { // If exists a valid owner than do a request to claim the given device new Thread() { @Override public void run() { try { Boolean result = claimDevice(device); dispatchSuccess(callback, result); } catch (InvalidDeviceOwnerStateException | KnotException e) { dispatchError(callback, e); } } }.start(); } /** * Update an existent device * * @param device the identifier of device (uuid) * @return the object updated * @throws KnotException KnotException */ public <T extends AbstractThingDevice> T updateDevice(String id, T device) throws InvalidDeviceOwnerStateException, KnotException { // Check if the current state of device owner is valid if (!isValidDeviceOwner()) { throw new InvalidDeviceOwnerStateException("The device owner is invalid or null"); } // Update the given device final String endPoint = mEndPoint + DEVICE_PATH + id; String json = mGson.toJson(device); RequestBody body = createRequestBodyWith(json); Request request = generateBasicRequestBuild(this.abstractDeviceOwner.getUuid(), this.abstractDeviceOwner.getToken(), endPoint).put(body).build(); try { Response response = mHttpClient.newCall(request).execute(); return (T) mGson.fromJson(response.body().string(), device.getClass()); } catch (Exception e) { throw new KnotException(e); } } /** * Async version of {@link #updateDevice(String, AbstractThingDevice)} * * @param device the identifier of device (uuid) * @param callback Callback for this method */ public <T extends AbstractThingDevice> void updateDevice(final String id, final T device, final Event<T> callback) { new Thread() { @Override public void run() { try { T result = updateDevice(id, device); dispatchSuccess(callback, result); } catch (InvalidDeviceOwnerStateException | KnotException e) { dispatchError(callback, e); } } }.start(); } /** * Delete a device from Meshblu instance. If the device is an orphan one, * anyone can delete it. However if the device has an owner, * only it can execute this action. * * @param device the device identifier (uuid) * @return a boolean to indicate if the device was deleted * @throws KnotException KnotException */ public boolean deleteDevice(String device) throws InvalidDeviceOwnerStateException, KnotException { // Check if the current state of device owner is valid if (!isValidDeviceOwner()) { throw new InvalidDeviceOwnerStateException("The device owner is invalid or null"); } // delete the given device final String endPoint = mEndPoint + DEVICE_PATH + device; Request request = generateBasicRequestBuild(this.abstractDeviceOwner.getUuid(), this.abstractDeviceOwner.getToken(), endPoint).delete().build(); try { Response response = mHttpClient.newCall(request).execute(); return response.code() == HttpURLConnection.HTTP_OK; } catch (Exception e) { throw new KnotException(e); } } /** * Async version of {@link ThingApi#deleteDevice(String)} * * @param device the device identifier (uuid) * @param callback Callback for this method */ public void deleteDevice(final String device, final Event<Boolean> callback) { new Thread() { @Override public void run() { try { Boolean result = deleteDevice(device); dispatchSuccess(callback, result); } catch (KnotException | InvalidDeviceOwnerStateException e) { dispatchError(callback, e); } } }.start(); } /** * Get all information regarding the device. * * @param clazz The class for this device. Meshblu works with any type of objects and * it is necessary deserialize the return to a valid object. * Note: The class parameter should be a extension of {@link AbstractThingDevice} * @return an json element containing device informations * @throws KnotException KnotException */ public <T extends AbstractThingDevice> T whoAmI(Class<T> clazz) throws InvalidDeviceOwnerStateException, KnotException { // Check if the current state of device owner is valid if (!isValidDeviceOwner()) { throw new InvalidDeviceOwnerStateException("The device owner is invalid or null"); } // Get all information regarding the given device final String endPoint = mEndPoint + WHOAMI; Request request = generateBasicRequestBuild(this.abstractDeviceOwner.getUuid(), this.abstractDeviceOwner.getToken(), endPoint).build(); try { Response response = mHttpClient.newCall(request).execute(); JsonElement jsonElement = new JsonParser().parse(response.body().string()); return mGson.fromJson(jsonElement.toString(), clazz); } catch (Exception e) { throw new KnotException(e); } } /** * Async version of {@link #whoAmI(Class)} * * @param clazz The class for this device. Meshblu works with any type of objects and * it is necessary deserialize the return to a valid object. * Note: The class parameter should be a extension of {@link AbstractThingDevice} * @param callback Callback for this method * @return an object based on the class parameter */ public <T extends AbstractThingDevice> void whoAmI(final Class<T> clazz, final Event<T> callback) { new Thread() { @Override public void run() { try { T result = whoAmI(clazz); dispatchSuccess(callback, result); } catch (KnotException | InvalidDeviceOwnerStateException e) { dispatchError(callback, e); } } }.start(); } /** * Get a specific device from Meshblu instance. * * @param device the device identifier (uuid) * @param clazz The class for this device. Meshblu works with any type of objects and * it is necessary deserialize the return to a valid object. * Note: The class parameter should be a extension of {@link AbstractThingDevice} * @return an object based on the class parameter * @throws KnotException KnotException */ public <T extends AbstractThingDevice> T getDevice(String device, Class<T> clazz) throws InvalidDeviceOwnerStateException, KnotException { // Check if the current state of device owner is valid if (!isValidDeviceOwner()) { throw new InvalidDeviceOwnerStateException("The device owner is invalid or null"); } // Get the specific device from meshblue instance final String endPoint = mEndPoint + DEVICE_PATH + device; Request request = generateBasicRequestBuild(this.abstractDeviceOwner.getUuid(), this.abstractDeviceOwner.getToken(), endPoint).build(); try { Response response = mHttpClient.newCall(request).execute(); JsonElement jsonElement = new JsonParser().parse(response.body().string()); JsonArray jsonArray = jsonElement.getAsJsonObject().getAsJsonArray(JSON_DEVICES); if (jsonArray.size() == 0) { return null; } return mGson.fromJson(jsonArray.get(0).toString(), clazz); } catch (Exception e) { throw new KnotException(e); } } /** * Async version of {@link #getDevice(String, Class)} * * @param device the device identifier (uuid) * @param clazz The class for this device. Meshblu works with any type of objects and * it is necessary deserialize the return to a valid object. * Note: The class parameter should be a extension of {@link AbstractThingDevice} * @param callback Callback for this method * @return an object based on the class parameter */ public <T extends AbstractThingDevice> void getDevice(final String device, final Class<T> clazz, final Event<T> callback) { new Thread() { @Override public void run() { try { T result = getDevice(device, clazz); dispatchSuccess(callback, result); } catch (KnotException | InvalidDeviceOwnerStateException e) { dispatchError(callback, e); } } }.start(); } /** * Get a specific device's gateway from Meshblu instance. * * @param device the device identifier (uuid) * @param clazz The class for this device. Meshblu works with any type of objects and * it is necessary deserialize the return to a valid object. * Note: The class parameter should be a extension of {@link AbstractThingDevice} * @return an object based on the class parameter * @throws KnotException KnotException */ public <T extends AbstractThingDevice> T getDeviceGateway(String device, Class<T> clazz) throws InvalidDeviceOwnerStateException, KnotException { // Check if the current state of device owner is valid if (!isValidDeviceOwner()) { throw new InvalidDeviceOwnerStateException("The device owner is invalid or null"); } // Get a specific device's gateway from Meshblu instance. final String endPoint = mEndPoint + DEVICE_PATH + device + DEVICE_PROPERTY_PATH_GATEWAY; Request request = generateBasicRequestBuild(this.abstractDeviceOwner.getUuid(), this.abstractDeviceOwner.getToken(), endPoint).build(); try { Response response = mHttpClient.newCall(request).execute(); JsonElement jsonElement = new JsonParser().parse(response.body().string()); JsonArray jsonArray = jsonElement.getAsJsonObject().getAsJsonArray(JSON_DEVICES); if (jsonArray.size() == 0) { return null; } return mGson.fromJson(jsonArray.get(0).toString(), clazz); } catch (Exception e) { throw new KnotException(e); } } /** * Async version of {@link #getDeviceGateway(String, Class)} * * @param device the device identifier (uuid) * @param clazz The class for this device. Meshblu works with any type of objects and * it is necessary deserialize the return to a valid object. * Note: The class parameter should be a extension of {@link AbstractThingDevice} * @param callback Callback for this method * @return an object based on the class parameter */ public <T extends AbstractThingDevice> void getDeviceGateway(final String device, final Class<T> clazz, final Event<T> callback) { new Thread() { @Override public void run() { try { T result = getDeviceGateway(device, clazz); dispatchSuccess(callback, result); } catch (KnotException | InvalidDeviceOwnerStateException e) { dispatchError(callback, e); } } }.start(); } /** * Get all devices those are claimed by one owner * * @param type object that will define what elements will returned by this method * @return a List with all devices those belongs to the owner * @throws KnotException KnotException */ public <T extends AbstractThingDevice> List<T> getDeviceList(final KnotList<T> type) throws InvalidDeviceOwnerStateException, KnotException { // Check if the current state of device owner is valid if (!isValidDeviceOwner()) { throw new InvalidDeviceOwnerStateException("The device owner is invalid or null"); } // Get all devices those are claimed by one owner final String endPoint = mEndPoint + MY_DEVICES_PATH; Request request = generateBasicRequestBuild(this.abstractDeviceOwner.getUuid(), this.abstractDeviceOwner.getToken(), endPoint).build(); try { Response response = mHttpClient.newCall(request).execute(); if (response.code() != HttpURLConnection.HTTP_OK) { return null; } JsonElement jsonElement = new JsonParser().parse(response.body().string()); JsonArray jsonArray = jsonElement.getAsJsonObject().getAsJsonArray(JSON_DEVICES); return mGson.fromJson(jsonArray.toString(), type); } catch (Exception e) { throw new KnotException(e); } } /** * Async version of {@link #getDeviceList(KnotList<T>)} * * @param type object that will define what elements will returned by this method * @param callback callback * @return a List with all devices those belongs to the owner * @throws KnotException KnotException */ public <T extends AbstractThingDevice> void getDeviceList(final KnotList<T> type, final Event<List<T>> callback) { new Thread() { @Override public void run() { try { List<T> result = getDeviceList(type); dispatchSuccess(callback, result); } catch (KnotException | InvalidDeviceOwnerStateException e) { dispatchError(callback, e); } } }.start(); } // Data methods /** * Create data for one device. If the device has an owner, it is necessary that the owner * param be the same of the device owner. * * @param device the device identifier (uuid) * @param data data that will be created for device * @return a boolean value to indicate if the data could be create for device * @throws KnotException KnotException */ public <T extends AbstractThingData> boolean createData(String device, T data) throws InvalidDeviceOwnerStateException, KnotException { // Check if the current state of device owner is valid if (!isValidDeviceOwner()) { throw new InvalidDeviceOwnerStateException("The device owner is invalid or null"); } // Create data for one device. If the device has an owner, it is necessary that the owner // param be the same of the device owner final String endPoint = mEndPoint + DATA_PATH + device; String json = mGson.toJson(data); RequestBody body = createRequestBodyWith(json); Request request = generateBasicRequestBuild(this.abstractDeviceOwner.getUuid(), this.abstractDeviceOwner.getToken(), endPoint).post(body).build(); try { Response response = mHttpClient.newCall(request).execute(); return response.code() == HttpURLConnection.HTTP_CREATED; } catch (Exception e) { throw new KnotException(e); } } /** * Async version of {@link #createData(String, AbstractThingData)} * * @param device the device identifier (uuid) * @param data data that will be created for device * @param callback Callback for this method * @return a boolean value to indicate if the data could be create for device * @throws KnotException */ public <T extends AbstractThingData> void createData(final String device, final T data, final Event<Boolean> callback) { new Thread() { @Override public void run() { try { Boolean result = createData(device, data); dispatchSuccess(callback, result); } catch (KnotException | InvalidDeviceOwnerStateException e) { dispatchError(callback, e); } } }.start(); } /** * @param device the device identifier (uuid) * @param type object that will define what elements will returned by this method * @param knotQueryData Date query * @return a List with data of the device * @throws KnotException KnotException */ public <T extends AbstractThingData> List<T> getDataList(String device, final KnotList<T> type, KnotQueryData knotQueryData) throws InvalidDeviceOwnerStateException, KnotException { // Check if the current state of device owner is valid if (!isValidDeviceOwner()) { throw new InvalidDeviceOwnerStateException("The device owner is invalid or null"); } final String endPoint = mEndPoint + DATA_PATH + device + getDataParameter(knotQueryData); Request request = generateBasicRequestBuild(this.abstractDeviceOwner.getUuid(), this.abstractDeviceOwner.getToken(), endPoint).build(); try { Response response = mHttpClient.newCall(request).execute(); JsonElement jsonElement = new JsonParser().parse(response.body().string()); JsonArray jsonArray = jsonElement.getAsJsonObject().getAsJsonArray(JSON_DATA); if (jsonArray == null || jsonArray.size() == 0) { return mGson.fromJson(EMPTY_ARRAY, type); } return mGson.fromJson(jsonArray.toString(), type); } catch (Exception e) { throw new KnotException(e); } } /** * Async version of {@link #getDataList(String, KnotList, KnotQueryData, Event)} * * @param device the device identifier (uuid) * @param type object that will define what elements will returned by this method * @param callback Callback for this method * @param knotQueryData Date query * @return a List with data of the device */ public <T extends AbstractThingData> void getDataList(final String device, final KnotList<T> type, final KnotQueryData knotQueryData, final Event<List<T>> callback) { new Thread() { @Override public void run() { try { List<T> result = getDataList(device, type, knotQueryData); dispatchSuccess(callback, result); } catch (KnotException | InvalidDeviceOwnerStateException e) { dispatchError(callback, e); } } }.start(); } //Message /** * Send a message in Meshblu instance * * @param message model sample to create a new message. Basically this message model * contains attributes that will be send into Meshblu. * @return New message with meshblu content. * @throws KnotException KnotException * @see AbstractThingMessage */ public <T extends AbstractThingMessage> T sendMessage(T message) throws KnotException, InvalidDeviceOwnerStateException { // Check if the current state of device owner is valid if (!isValidDeviceOwner()) { throw new InvalidDeviceOwnerStateException("The device owner is invalid or null"); } final String endPoint = mEndPoint + MESSAGE; String json = mGson.toJson(message); RequestBody body = createRequestBodyWith(json); Request request = generateBasicRequestBuild(this.abstractDeviceOwner.getUuid(), this.abstractDeviceOwner.getToken(), endPoint).post(body).build(); try { Response response = mHttpClient.newCall(request).execute(); JsonElement jsonElement = new JsonParser().parse(response.body().string()); return (T) mGson.fromJson(jsonElement.toString(), message.getClass()); } catch (Exception e) { throw new KnotException(e); } } /** * Send a message in Meshblu instance * * @param message model sample to create a new message. Basically this message model * contains attributes that will be send into Meshblu. * @return New message with meshblu content. * @see AbstractThingMessage */ public <T extends AbstractThingMessage> void sendMessage(final T message, final Event<T> callback) { new Thread() { @Override public void run() { try { T result = sendMessage(message); dispatchSuccess(callback, result); } catch (final KnotException | InvalidDeviceOwnerStateException e) { dispatchError(callback, e); } } }.start(); } /////////////////////////////////////////////////////////////////////////// // Private methods /////////////////////////////////////////////////////////////////////////// private RequestBody createEmptyRequestBody() { return RequestBody.create(JSON, EMPTY_JSON); } private RequestBody createRequestBodyWith(String data) { return RequestBody.create(JSON, data); } private Request.Builder generateBasicRequestBuild(String uuid, String token, String endPoint) { return new Request.Builder().addHeader(HEADER_AUTH_UUID, uuid).addHeader(HEADER_AUTH_TOKEN, token) .url(endPoint); } private Request.Builder generateBasicRequestBuild(String endPoint) { return new Request.Builder().url(endPoint); } private <T> void dispatchSuccess(final Event<T> callback, final T result) { mMainHandler.post(new Runnable() { @Override public void run() { callback.onEventFinish(result); } }); } private <T> void dispatchError(final Event<T> callback, final Exception error) { mMainHandler.post(new Runnable() { @Override public void run() { callback.onEventError(error); } }); } /** * Check if the current Owner device is valid * * @return true if the owner device is valid */ public boolean isValidDeviceOwner() { return this.abstractDeviceOwner != null && !TextUtils.isEmpty(this.abstractDeviceOwner.getUuid()) && !TextUtils.isEmpty(this.abstractDeviceOwner.getToken()); } /** * This method is used to build the parameters * @param knotQueryData object with query information * @return String that represents a list of parameter */ private String getDataParameter(KnotQueryData knotQueryData) { String parameter = "?"; String and = "&"; int amountOfKnotData = -1; if (knotQueryData != null) { if (knotQueryData.getLimit() > 0) { amountOfKnotData = knotQueryData.getLimit(); } parameter = parameter + LIMIT + EQUAL + amountOfKnotData; if (knotQueryData.getStartDate() != null) { parameter = parameter + and + DATE_START + EQUAL + DateUtils.getTimeStamp(knotQueryData.getStartDate()); } if (knotQueryData.getFinishDate() != null) { parameter = parameter + and + DATE_FINISH + EQUAL + DateUtils.getTimeStamp(knotQueryData.getFinishDate()); } } return parameter.trim(); } }