Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.zazaz.iot.bosch.indego; import java.io.IOException; import org.apache.commons.codec.binary.Base64; import org.apache.http.HttpStatus; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import com.fasterxml.jackson.databind.ObjectMapper; /** * Instances of this class handle the communcation the the Indego device and provices methods * to control and query the device. */ public class IndegoController { /** the default url which provices the service for controlling the device */ public static final String BASE_URL_DEFAULT = "https://api.indego.iot.bosch-si.com/api/v1/"; /** for limiting the amount of status query requests on the server a minimum interval is specified */ public static final long MIN_STATE_QUERY_INTERVAL_MS = 60 * 1000; /** the url which provices the service for controlling the device */ private final String baseUrl; /** for mapping between JSON strings and POJOs */ private final ObjectMapper mapper = new ObjectMapper(); /** the encoded authenticated string for basic authentication */ private final String authentication; /** the http client instance for communicating to the server */ private CloseableHttpClient httpClient; /** the respons, which was sent by the server after a successfull authentication (contains session, device serial, etc.) */ private AuthenticationResponse session; /** the last timestamp, when the device status was queried */ private long lastStateQueryTs; /** this stores the result of the last device status query */ private DeviceStateInformation deviceStateCache; /** * This initializes the controller instance, but does not connect yet. * * @param baseUrl_ the url which provices the service for controlling the device; * if null, the default base url is used * @param username_ the username for authenticating * @param password_ the password for authenticating */ public IndegoController(String baseUrl_, String username_, String password_) { baseUrl = baseUrl_ == null ? BASE_URL_DEFAULT : normalizeBaseUrl(baseUrl_); authentication = Base64.encodeBase64String((username_ + ":" + password_).getBytes()); } /** * This initializes the controller instance, but does not connect yet. * * @param username_ the username for authenticating * @param password_ the password for authenticating */ public IndegoController(String username_, String password_) { baseUrl = BASE_URL_DEFAULT; authentication = Base64.encodeBase64String((username_ + ":" + password_).getBytes()); } /** * @param baseUrl_ the user specified base url * @return the reformatted and normalized base url */ private String normalizeBaseUrl(String baseUrl_) { return baseUrl_.endsWith("/") ? baseUrl_ : baseUrl_ + "/"; } /** * This connects to the server and authenticates the session. * * @throws IndegoAuthenticationException in case of wrong authentication informations * @throws IndegoException in case of any unexpected event */ public void connect() throws IndegoAuthenticationException, IndegoException { if (httpClient != null) { throw new IndegoException("You are already connected"); } try { httpClient = HttpClients.createDefault(); session = doAuthenticate(); lastStateQueryTs = 0; } catch (IndegoException ex) { safeCloseClient(); throw ex; } catch (Exception ex) { safeCloseClient(); throw new IndegoException(ex); } } /** * This disconnects from the server und shuts down the session. */ public void disconnect() { safeCloseClient(); } /** * @return the serial number of the associated Indego device * @throws IndegoException in case of any unexpected event */ public String getDeviceSerialNumber() throws IndegoException { return session.getAlmSn(); } /** * This queries the device state from the server or returns a cached state if the * last query was lass than <code>MIN_STATE_QUERY_INTERVAL_MS</code> milliseconds ago. * * @return the device state * @throws IndegoException in case of any unexpected event */ public DeviceStateInformation getState() throws IndegoException { synchronized (this) { if (deviceStateCache != null && System.currentTimeMillis() - MIN_STATE_QUERY_INTERVAL_MS < lastStateQueryTs) { return deviceStateCache; } DeviceStateInformation state = doGetRequest("alms/" + session.getAlmSn() + "/state", DeviceStateInformation.class); deviceStateCache = state; lastStateQueryTs = System.currentTimeMillis(); return state; } } public DeviceCalendar getCalendar() throws IndegoException { synchronized (this) { DeviceCalendar calendar = doGetRequest("alms/" + session.getAlmSn() + "/calendar", DeviceCalendar.class); return calendar; } } /** * This sends a command to the Indego device. * * @param command_ the control command to send to the device. * @throws IndegoInvalidCommandException if the command was not processed correctly * @throws IndegoException in case of any unexpected event */ public void sendCommand(DeviceCommand command_) throws IndegoInvalidCommandException, IndegoException { lastStateQueryTs = 0; SetStateRequest request = new SetStateRequest(); request.setState(command_.getActionCode()); doPutRequest("alms/" + session.getAlmSn() + "/state", request, null); } /** * Closes the connection and takes care of error handling. */ private void safeCloseClient() { try { if (httpClient != null) { httpClient.close(); } } catch (IOException ex) { // Ignored } httpClient = null; } /** * This sends an authentication request to the server and unmarshals the result. * * @return the result of the authentication request, when the authentication was * successfull. * @throws IndegoAuthenticationException in case of wrong authentication informations * @throws IndegoException in case of any unexpected event */ private AuthenticationResponse doAuthenticate() throws IndegoAuthenticationException, IndegoException { try { HttpPost httpPost = new HttpPost(baseUrl + "authenticate"); httpPost.addHeader("Authorization", "Basic " + authentication); AuthenticationRequest authRequest = new AuthenticationRequest(); authRequest.setDevice(""); authRequest.setOsType("Android"); authRequest.setOsVersion("4.0"); authRequest.setDeviceManufacturer("unknown"); authRequest.setDeviceType("unknown"); String json = mapper.writeValueAsString(authRequest); httpPost.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON)); CloseableHttpResponse response = httpClient.execute(httpPost); int status = response.getStatusLine().getStatusCode(); if (status == HttpStatus.SC_UNAUTHORIZED) { throw new IndegoAuthenticationException("Was not able to authenticate"); } if (status != HttpStatus.SC_OK && status != HttpStatus.SC_CREATED) { throw new IndegoAuthenticationException( "The request failed with error: " + response.getStatusLine().toString()); } String responseContents = EntityUtils.toString(response.getEntity()); AuthenticationResponse authResponse = mapper.readValue(responseContents, AuthenticationResponse.class); return authResponse; } catch (IOException ex) { throw new IndegoException(ex); } } /** * This sends a GET request to the server and unmarshals the JSON result. * * @param urlSuffix the path, to which the request should be sent * @param returnType the class to which the JSON result should be mapped; if null, * no mapping is tried and null is returned. * @return the mapped result of the request * @throws IndegoException in case of any unexpected event */ private <T> T doGetRequest(String urlSuffix, Class<? extends T> returnType) throws IndegoException { try { HttpGet httpRequest = new HttpGet(baseUrl + urlSuffix); httpRequest.setHeader("x-im-context-id", session.getContextId()); CloseableHttpResponse response = httpClient.execute(httpRequest); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { throw new IndegoAuthenticationException( "The request failed with error: " + response.getStatusLine().toString()); } String responseContents = EntityUtils.toString(response.getEntity()); if (returnType == null) { return null; } else { T result = mapper.readValue(responseContents, returnType); return result; } } catch (IOException ex) { throw new IndegoException(ex); } } /** * This sends a PUT request to the server and unmarshals the JSON result. * * @param urlSuffix the path, to which the request should be sent * @param request the data, which should be sent to the server (mapped to JSON) * @param returnType the class to which the JSON result should be mapped; if null, * no mapping is tried and null is returned. * @return the mapped result of the request * @throws IndegoException in case of any unexpected event */ private <T> T doPutRequest(String urlSuffix, Object request, Class<? extends T> returnType) throws IndegoException { try { HttpPut httpRequest = new HttpPut(baseUrl + urlSuffix); httpRequest.setHeader("x-im-context-id", session.getContextId()); String json = mapper.writeValueAsString(request); httpRequest.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON)); CloseableHttpResponse response = httpClient.execute(httpRequest); if (response.getStatusLine().getStatusCode() == HttpStatus.SC_INTERNAL_SERVER_ERROR) { throw new IndegoInvalidCommandException( "The request failed with error: " + response.getStatusLine().toString()); } if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { throw new IndegoAuthenticationException( "The request failed with error: " + response.getStatusLine().toString()); } String responseContents = EntityUtils.toString(response.getEntity()); if (returnType == null) { return null; } else { T result = mapper.readValue(responseContents, returnType); return result; } } catch (IOException ex) { throw new IndegoException(ex); } } }