From 6179298d9fa9b72eb73b88e1ab3e65f4615f63b7 Mon Sep 17 00:00:00 2001 From: Menaka Madushanka Date: Wed, 13 Jan 2016 11:11:55 +0530 Subject: [PATCH] Connected cup device added --- .../connectedcup/component/controller/pom.xml | 251 ++++ .../ConnectedCupControllerService.java | 180 +++ .../controller/service/dto/DeviceJSON.java | 35 + .../exception/ConnectedCupException.java | 31 + .../transport/ConnectedCupMQTTConnector.java | 204 +++ .../util/ConnectedCupServiceUtils.java | 216 +++ .../webapp/META-INF/webapp-classloading.xml | 34 + .../src/main/webapp/WEB-INF/cxf-servlet.xml | 48 + .../src/main/webapp/WEB-INF/web.xml | 69 + .../connectedcup/component/manager/pom.xml | 261 ++++ .../service/ConnectedCupManagerService.java | 243 ++++ .../webapp/META-INF/webapp-classloading.xml | 34 + .../src/main/webapp/WEB-INF/cxf-servlet.xml | 42 + .../manager/src/main/webapp/WEB-INF/web.xml | 69 + .../constants/ConnectedCupConstants.java | 22 + .../plugin/impl/dao/ConnectedCupDAO.java | 122 ++ .../impl/dao/impl/ConnectedCupDAOImpl.java | 242 ++++ .../impl/dao/util/ConnectedCupUtils.java | 45 + .../EventReceiver_coffeelevel.xml | 9 + .../artifact.xml | 4 + .../Eventstore_coffeelevel_1.0.0/artifact.xml | 4 + .../org_wso2_iot_devices_coffeelevel.xml | 44 + .../artifact.xml | 5 + ...rg.wso2.iot.devices.coffeelevel_1.0.0.json | 20 + .../CoffeeLevel_Sensor_Script.xml | 20 + .../Sparkscripts_1.0.0/artifact.xml | 4 + .../CoffeeLevel_Sensor/artifacts.xml | 12 + .../EventReceiver_temperature.xml | 8 + .../artifact.xml | 4 + .../Eventstore_temperature_1.0.0/artifact.xml | 4 + .../org_wso2_iot_devices_temperature.xml | 44 + .../artifact.xml | 5 + ...rg.wso2.iot.devices.temperature_1.0.0.json | 20 + .../Temperature_Sensor_Script.xml | 13 + .../Sparkscripts_1.0.0/artifact.xml | 4 + .../Temperature_Sensor/artifacts.xml | 11 + .../connectedcup/feature/analytics/build.xml | 25 + .../connectedcup/feature/analytics/pom.xml | 72 + .../feature/connectedcup-feature/pom.xml | 248 ++++ .../resources/agent/deviceConfig.properties | 29 + .../main/resources/agent/sketch.properties | 2 + .../src/main/resources/agent/start-device.sh | 190 +++ .../src/main/resources/build.properties | 1 + .../carbonapps/CoffeeLevel_Sensor.car | Bin 0 -> 4158 bytes .../carbonapps/Temperature_Sensor.car | Bin 0 -> 4138 bytes .../main/resources/configs/connectedcup.json | 30 + .../main/resources/configs/connectedcup.xml | 25 + .../database/ConnectedCupDM_DB.h2.db | Bin 0 -> 20480 bytes .../datasources/connectedcup-datasources.xml | 46 + .../src/main/resources/dbscripts/h2.sql | 13 + .../src/main/resources/dbscripts/mysql.sql | 14 + .../device-view.hbs | 100 ++ .../device-view.js | 36 + .../device-view.json | 3 + .../public/images/thumb.png | Bin 0 -> 5417 bytes .../private/conf/device-type.json | 7 + .../public/images/coffeecup.png | Bin 0 -> 5417 bytes .../public/images/schematicsGuide.png | Bin 0 -> 107251 bytes .../public/images/thumb.png | Bin 0 -> 5417 bytes .../public/js/download.js | 208 +++ .../public/js/jquery.validate.js | 1220 +++++++++++++++++ .../type-view.hbs | 256 ++++ .../type-view.json | 3 + .../src/main/resources/p2.inf | 16 + 64 files changed, 4927 insertions(+) create mode 100644 modules/samples/connectedcup/component/controller/pom.xml create mode 100644 modules/samples/connectedcup/component/controller/src/main/java/org/coffeeking/controller/service/ConnectedCupControllerService.java create mode 100644 modules/samples/connectedcup/component/controller/src/main/java/org/coffeeking/controller/service/dto/DeviceJSON.java create mode 100644 modules/samples/connectedcup/component/controller/src/main/java/org/coffeeking/controller/service/exception/ConnectedCupException.java create mode 100644 modules/samples/connectedcup/component/controller/src/main/java/org/coffeeking/controller/service/transport/ConnectedCupMQTTConnector.java create mode 100644 modules/samples/connectedcup/component/controller/src/main/java/org/coffeeking/controller/service/util/ConnectedCupServiceUtils.java create mode 100644 modules/samples/connectedcup/component/controller/src/main/webapp/META-INF/webapp-classloading.xml create mode 100644 modules/samples/connectedcup/component/controller/src/main/webapp/WEB-INF/cxf-servlet.xml create mode 100644 modules/samples/connectedcup/component/controller/src/main/webapp/WEB-INF/web.xml create mode 100644 modules/samples/connectedcup/component/manager/pom.xml create mode 100644 modules/samples/connectedcup/component/manager/src/main/java/org/coffeeking/manager/service/ConnectedCupManagerService.java create mode 100644 modules/samples/connectedcup/component/manager/src/main/webapp/META-INF/webapp-classloading.xml create mode 100644 modules/samples/connectedcup/component/manager/src/main/webapp/WEB-INF/cxf-servlet.xml create mode 100644 modules/samples/connectedcup/component/manager/src/main/webapp/WEB-INF/web.xml create mode 100644 modules/samples/connectedcup/component/plugin/src/main/java/org/coffeeking/connectedcup/plugin/constants/ConnectedCupConstants.java create mode 100644 modules/samples/connectedcup/component/plugin/src/main/java/org/coffeeking/connectedcup/plugin/impl/dao/ConnectedCupDAO.java create mode 100644 modules/samples/connectedcup/component/plugin/src/main/java/org/coffeeking/connectedcup/plugin/impl/dao/impl/ConnectedCupDAOImpl.java create mode 100644 modules/samples/connectedcup/component/plugin/src/main/java/org/coffeeking/connectedcup/plugin/impl/dao/util/ConnectedCupUtils.java create mode 100644 modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventreceiver_coffeelevel_1.0.0/EventReceiver_coffeelevel.xml create mode 100644 modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventreceiver_coffeelevel_1.0.0/artifact.xml create mode 100644 modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventstore_coffeelevel_1.0.0/artifact.xml create mode 100644 modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventstore_coffeelevel_1.0.0/org_wso2_iot_devices_coffeelevel.xml create mode 100644 modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventstream_coffeelevel_1.0.0/artifact.xml create mode 100644 modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventstream_coffeelevel_1.0.0/org.wso2.iot.devices.coffeelevel_1.0.0.json create mode 100644 modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Sparkscripts_1.0.0/CoffeeLevel_Sensor_Script.xml create mode 100644 modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Sparkscripts_1.0.0/artifact.xml create mode 100644 modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/artifacts.xml create mode 100644 modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventreceiver_temperature_1.0.0/EventReceiver_temperature.xml create mode 100644 modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventreceiver_temperature_1.0.0/artifact.xml create mode 100644 modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventstore_temperature_1.0.0/artifact.xml create mode 100644 modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventstore_temperature_1.0.0/org_wso2_iot_devices_temperature.xml create mode 100644 modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventstream_temperature_1.0.0/artifact.xml create mode 100644 modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventstream_temperature_1.0.0/org.wso2.iot.devices.temperature_1.0.0.json create mode 100644 modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Sparkscripts_1.0.0/Temperature_Sensor_Script.xml create mode 100644 modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Sparkscripts_1.0.0/artifact.xml create mode 100644 modules/samples/connectedcup/feature/analytics/Temperature_Sensor/artifacts.xml create mode 100644 modules/samples/connectedcup/feature/analytics/build.xml create mode 100644 modules/samples/connectedcup/feature/analytics/pom.xml create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/pom.xml create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/agent/deviceConfig.properties create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/agent/sketch.properties create mode 100755 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/agent/start-device.sh create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/build.properties create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/carbonapps/CoffeeLevel_Sensor.car create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/carbonapps/Temperature_Sensor.car create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/configs/connectedcup.json create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/configs/connectedcup.xml create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/database/ConnectedCupDM_DB.h2.db create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/datasources/connectedcup-datasources.xml create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/dbscripts/h2.sql create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/dbscripts/mysql.sql create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.device-view/device-view.hbs create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.device-view/device-view.js create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.device-view/device-view.json create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.device-view/public/images/thumb.png create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/private/conf/device-type.json create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/public/images/coffeecup.png create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/public/images/schematicsGuide.png create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/public/images/thumb.png create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/public/js/download.js create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/public/js/jquery.validate.js create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/type-view.hbs create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/type-view.json create mode 100644 modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/p2.inf diff --git a/modules/samples/connectedcup/component/controller/pom.xml b/modules/samples/connectedcup/component/controller/pom.xml new file mode 100644 index 00000000..82102873 --- /dev/null +++ b/modules/samples/connectedcup/component/controller/pom.xml @@ -0,0 +1,251 @@ + + + + + + device-mgt-iot-connectedcup + org.wso2.carbon.devicemgt-plugins + 1.9.2-SNAPSHOT + ../pom.xml + + + + 4.0.0 + org.coffeeking.controller.service + 1.9.2-SNAPSHOT + war + WSO2 IoTS(Device Types) - Connected Cup Controller Service + WSO2 IoTS(Device Types) - Connected Cup Controller Service + http://wso2.org + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.core + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.analytics + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.certificate.mgt.core + + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + + + org.apache.cxf + cxf-rt-frontend-jaxrs + + + org.apache.cxf + cxf-rt-transports-http + + + + + org.eclipse.paho + mqtt-client + + + + + org.apache.httpcomponents + httpasyncclient + 4.1 + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot + + + + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-jaxrs + + + javax + javaee-web-api + + + javax.ws.rs + jsr311-api + + + commons-httpclient.wso2 + commons-httpclient + + + + org.wso2.carbon + org.wso2.carbon.utils + + + org.bouncycastle.wso2 + bcprov-jdk15on + + + org.wso2.carbon + org.wso2.carbon.user.api + + + org.wso2.carbon + org.wso2.carbon.queuing + + + org.wso2.carbon + org.wso2.carbon.base + + + org.apache.axis2.wso2 + axis2 + + + org.igniterealtime.smack.wso2 + smack + + + org.igniterealtime.smack.wso2 + smackx + + + jaxen + jaxen + + + commons-fileupload.wso2 + commons-fileupload + + + org.apache.ant.wso2 + ant + + + org.apache.ant.wso2 + ant + + + commons-httpclient.wso2 + commons-httpclient + + + org.eclipse.equinox + javax.servlet + + + org.wso2.carbon + org.wso2.carbon.registry.api + + + + + + org.igniterealtime.smack.wso2 + smack + + + org.igniterealtime.smack.wso2 + smackx + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.annotations + + + org.wso2.carbon.devicemgt-plugins + org.coffeeking.connectedcup.plugin + + + + org.json.wso2 + json + + + + + + + ${basedir}/src/main/java + + + + + maven-compiler-plugin + + 1.7 + 1.7 + + 2.3.2 + + + maven-war-plugin + + connectedcup + + + + + + \ No newline at end of file diff --git a/modules/samples/connectedcup/component/controller/src/main/java/org/coffeeking/controller/service/ConnectedCupControllerService.java b/modules/samples/connectedcup/component/controller/src/main/java/org/coffeeking/controller/service/ConnectedCupControllerService.java new file mode 100644 index 00000000..525dbbd4 --- /dev/null +++ b/modules/samples/connectedcup/component/controller/src/main/java/org/coffeeking/controller/service/ConnectedCupControllerService.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.coffeeking.controller.service; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.coffeeking.connectedcup.plugin.constants.ConnectedCupConstants; +import org.coffeeking.controller.service.dto.DeviceJSON; +import org.coffeeking.controller.service.transport.ConnectedCupMQTTConnector; +import org.coffeeking.controller.service.util.ConnectedCupServiceUtils; +import org.json.JSONObject; +import org.wso2.carbon.apimgt.annotations.api.API; +import org.wso2.carbon.apimgt.annotations.device.DeviceType; +import org.wso2.carbon.apimgt.annotations.device.feature.Feature; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.iot.DeviceValidator; +import org.wso2.carbon.device.mgt.iot.controlqueue.mqtt.MqttConfig; +import org.wso2.carbon.device.mgt.iot.exception.DeviceControllerException; +import org.wso2.carbon.device.mgt.iot.sensormgt.SensorDataManager; +import org.wso2.carbon.device.mgt.iot.sensormgt.SensorRecord; +import org.wso2.carbon.device.mgt.iot.transport.TransportHandlerException; + +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.Calendar; + +@API( name="connectedcup", version="1.0.0", context="/connectedcup") +@DeviceType( value = "connectedcup") +public class ConnectedCupControllerService { + + private static Log log = LogFactory.getLog(ConnectedCupControllerService.class); + private static final String SUPER_TENANT = "carbon.super"; + private static ConnectedCupMQTTConnector connectedCupMQTTConnector; + + @Context + HttpServletResponse response; + + + public ConnectedCupMQTTConnector connectedCupMQTTConnector() { + return ConnectedCupControllerService.connectedCupMQTTConnector; + } + + public void setconnectedCupMQTTConnector( + ConnectedCupMQTTConnector connectedCupMQTTConnector) { + ConnectedCupControllerService.connectedCupMQTTConnector = connectedCupMQTTConnector; + if (MqttConfig.getInstance().isEnabled()) { + connectedCupMQTTConnector.connect(); + } else { + log.warn("MQTT disabled in 'devicemgt-config.xml'. " + + "Hence, DigitalDisplayMqttCommunicationHandler not started."); + } + } + + /** + * @param deviceId + * @param owner + */ + @Path("controller/cup/coffeelevel") + @GET + public SensorRecord readCoffeeLevel(@HeaderParam("owner") String owner, + @HeaderParam("deviceId") String deviceId, + @Context HttpServletResponse response) { + SensorRecord sensorRecord = null; + DeviceValidator deviceValidator = new DeviceValidator(); + try { + if (!deviceValidator.isExist(owner, SUPER_TENANT, new DeviceIdentifier( + deviceId, ConnectedCupConstants.DEVICE_TYPE))) { + response.setStatus(Response.Status.UNAUTHORIZED.getStatusCode()); + } + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + + + if (log.isDebugEnabled()) { + log.debug("Sending request to read liquid level value of device [" + deviceId + "] via MQTT"); + } + + try { + String mqttResource = ConnectedCupConstants.LEVEL_CONTEXT.replace("/", ""); + connectedCupMQTTConnector.publishDeviceData(owner, deviceId, mqttResource, ""); + + sensorRecord = SensorDataManager.getInstance().getSensorRecord(deviceId, + ConnectedCupConstants.SENSOR_LEVEL); + } catch ( DeviceControllerException | TransportHandlerException e ) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + + response.setStatus(Response.Status.OK.getStatusCode()); + return sensorRecord; + } + + + + @Path("controller/cup/temperature") + @GET + public SensorRecord readTemperature(@HeaderParam("owner") String owner, + @HeaderParam("deviceId") String deviceId, + @Context HttpServletResponse response) { + SensorRecord sensorRecord = null; + + DeviceValidator deviceValidator = new DeviceValidator(); + try { + if (!deviceValidator.isExist(owner, SUPER_TENANT, + new DeviceIdentifier(deviceId, ConnectedCupConstants.DEVICE_TYPE))) { + response.setStatus(Response.Status.UNAUTHORIZED.getStatusCode()); + } + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + + if (log.isDebugEnabled()) { + log.debug("Sending request to read connected cup temperature of device " + + "[" + deviceId + "] via MQTT"); + } + + try { + String mqttResource = ConnectedCupConstants.TEMPERATURE_CONTEXT.replace("/", ""); + connectedCupMQTTConnector.publishDeviceData(owner, deviceId, mqttResource, ""); + + sensorRecord = SensorDataManager.getInstance().getSensorRecord(deviceId, + ConnectedCupConstants.SENSOR_TEMPERATURE); + } catch ( DeviceControllerException | TransportHandlerException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + + response.setStatus(Response.Status.OK.getStatusCode()); + return sensorRecord; + } + + @Path("controller/ordercoffee") + @POST + public HttpServletResponse orderCoffee(@QueryParam("deviceId") String deviceId, @QueryParam("deviceOwner") String + deviceOwner, @Context HttpServletResponse response){ + SensorRecord sensorRecord = null; + DeviceValidator deviceValidator = new DeviceValidator(); + try { + if (!deviceValidator.isExist(deviceOwner, SUPER_TENANT, new DeviceIdentifier( + deviceId, ConnectedCupConstants.DEVICE_TYPE))) { + response.setStatus(Response.Status.UNAUTHORIZED.getStatusCode()); + } + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + + response.setStatus(Response.Status.ACCEPTED.getStatusCode()); + log.info("Coffee ordered....!"); + + if (log.isDebugEnabled()) { + log.debug("Sending request to read liquid level value of device [" + deviceId + "] via MQTT"); + } + return response; + } +} \ No newline at end of file diff --git a/modules/samples/connectedcup/component/controller/src/main/java/org/coffeeking/controller/service/dto/DeviceJSON.java b/modules/samples/connectedcup/component/controller/src/main/java/org/coffeeking/controller/service/dto/DeviceJSON.java new file mode 100644 index 00000000..806de48d --- /dev/null +++ b/modules/samples/connectedcup/component/controller/src/main/java/org/coffeeking/controller/service/dto/DeviceJSON.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.coffeeking.controller.service.dto; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement +@JsonIgnoreProperties(ignoreUnknown = true) +public class DeviceJSON { + @XmlElement(required = true) public String owner; + @XmlElement(required = true) public String deviceId; + @XmlElement(required = true) public String reply; + @XmlElement public Long time; + @XmlElement public String key; + @XmlElement public float value; +} diff --git a/modules/samples/connectedcup/component/controller/src/main/java/org/coffeeking/controller/service/exception/ConnectedCupException.java b/modules/samples/connectedcup/component/controller/src/main/java/org/coffeeking/controller/service/exception/ConnectedCupException.java new file mode 100644 index 00000000..1c6caf78 --- /dev/null +++ b/modules/samples/connectedcup/component/controller/src/main/java/org/coffeeking/controller/service/exception/ConnectedCupException.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.coffeeking.controller.service.exception; + +public class ConnectedCupException extends Exception { + private static final long serialVersionUID = 118512086957330189L; + + public ConnectedCupException(String errorMessage) { + super(errorMessage); + } + + public ConnectedCupException(String errorMessage, Throwable throwable) { + super(errorMessage, throwable); + } +} diff --git a/modules/samples/connectedcup/component/controller/src/main/java/org/coffeeking/controller/service/transport/ConnectedCupMQTTConnector.java b/modules/samples/connectedcup/component/controller/src/main/java/org/coffeeking/controller/service/transport/ConnectedCupMQTTConnector.java new file mode 100644 index 00000000..293c1b5e --- /dev/null +++ b/modules/samples/connectedcup/component/controller/src/main/java/org/coffeeking/controller/service/transport/ConnectedCupMQTTConnector.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.coffeeking.controller.service.transport; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.coffeeking.connectedcup.plugin.constants.ConnectedCupConstants; +import org.coffeeking.controller.service.util.ConnectedCupServiceUtils; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.wso2.carbon.device.mgt.iot.config.server.DeviceManagementConfigurationManager; +import org.wso2.carbon.device.mgt.iot.controlqueue.mqtt.MqttConfig; +import org.wso2.carbon.device.mgt.iot.sensormgt.SensorDataManager; +import org.wso2.carbon.device.mgt.iot.transport.TransportHandlerException; +import org.wso2.carbon.device.mgt.iot.transport.mqtt.MQTTTransportHandler; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.Calendar; +import java.util.UUID; + +@SuppressWarnings("no JAX-WS annotation") +public class ConnectedCupMQTTConnector extends MQTTTransportHandler { + private static Log log = LogFactory.getLog(ConnectedCupMQTTConnector.class); + + private static String serverName = DeviceManagementConfigurationManager.getInstance(). + getDeviceManagementServerInfo().getName(); + + private static String subscribeTopic = "wso2" + File.separator + "+" + File.separator + + ConnectedCupConstants.DEVICE_TYPE + File.separator + "+" + File.separator + + "connected_publisher"; + + private static String iotServerSubscriber = UUID.randomUUID().toString().substring(0, 5); + + private ConnectedCupMQTTConnector() { + super(iotServerSubscriber, ConnectedCupConstants.DEVICE_TYPE, + MqttConfig.getInstance().getMqttQueueEndpoint(), subscribeTopic); + } + + @Override + public void connect() { + Runnable connector = new Runnable() { + public void run() { + while (!isConnected()) { + try { + connectToQueue(); + subscribeToQueue(); + } catch (TransportHandlerException e) { + log.warn("Connection/Subscription to MQTT Broker at: " + mqttBrokerEndPoint + " failed"); + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException ex) { + log.error("MQTT-Subscriber: Thread Sleep Interrupt Exception.", ex); + } + } + } + } + }; + + Thread connectorThread = new Thread(connector); + connectorThread.setDaemon(true); + connectorThread.start(); + } + + + + @Override + public void publishDeviceData(String... publishData) throws TransportHandlerException { + if (publishData.length != 4) { + String errorMsg = "Incorrect number of arguments received to SEND-MQTT Message. " + + "Need to be [owner, deviceId, resource{BULB/TEMP}, state{ON/OFF or null}]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg); + } + + String deviceOwner = publishData[0]; + String deviceId = publishData[1]; + String resource = publishData[2]; + String state = publishData[3]; + + MqttMessage pushMessage = new MqttMessage(); + String publishTopic = + serverName + File.separator + deviceOwner + File.separator + + ConnectedCupConstants.DEVICE_TYPE + File.separator + deviceId; + + try { + + String actualMessage = resource + ":" + state; + + pushMessage.setPayload(actualMessage.getBytes(StandardCharsets.UTF_8)); + pushMessage.setQos(DEFAULT_MQTT_QUALITY_OF_SERVICE); + pushMessage.setRetained(false); + + publishToQueue(publishTopic, pushMessage); + + } catch (Exception e) { + String errorMsg = "Preparing Secure payload failed for device - [" + deviceId + "] of owner - " + + "[" + deviceOwner + "]."; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } + } + + + @Override + public void processIncomingMessage(MqttMessage mqttMessage, String... strings) throws TransportHandlerException { + String topic = strings[0]; + + String ownerAndId = topic.replace("wso2" + File.separator + "iot" + File.separator, ""); + ownerAndId = ownerAndId.replace(File.separator + ConnectedCupConstants.DEVICE_TYPE + File.separator, ":"); + ownerAndId = ownerAndId.replace(File.separator + "connectedcup_publisher", ""); + + String owner = ownerAndId.split(":")[0]; + String deviceId = ownerAndId.split(":")[1]; + + String[] messageData = mqttMessage.toString().split(":"); + Float value = Float.valueOf(messageData[1]); + switch (messageData[0]){ + case "temperature": + SensorDataManager.getInstance().setSensorRecord(deviceId, ConnectedCupConstants.SENSOR_TEMPERATURE, + String.valueOf(messageData[1]), + Calendar.getInstance().getTimeInMillis()); + ConnectedCupServiceUtils.publishToDAS(owner, deviceId, value); + break; + case "coffeelevel": + SensorDataManager.getInstance().setSensorRecord(deviceId, ConnectedCupConstants.SENSOR_TEMPERATURE, + String.valueOf(messageData[1]), + Calendar.getInstance().getTimeInMillis()); + ConnectedCupServiceUtils.publishToDAS(owner, deviceId, value); + } + + log.info("Received MQTT message for OWNER: " + owner + " DEVICE.ID: " + deviceId + " | Command: " + + messageData[0] +" " + messageData[1] ); + + + } + + + @Override + public void disconnect() { + Runnable stopConnection = new Runnable() { + public void run() { + while (isConnected()) { + try { + closeConnection(); + } catch (MqttException e) { + if (log.isDebugEnabled()) { + log.warn("Unable to 'STOP' MQTT connection at broker at: " + mqttBrokerEndPoint + + " for device-type - " + ConnectedCupConstants.DEVICE_TYPE, e); + } + + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException e1) { + log.error("MQTT-Terminator: Thread Sleep Interrupt Exception at device-type - " + + ConnectedCupConstants.DEVICE_TYPE, e1); + } + } + } + } + }; + + Thread terminatorThread = new Thread(stopConnection); + terminatorThread.setDaemon(true); + terminatorThread.start(); + } + + @Override + public void publishDeviceData() { + // nothing to do + } + + + @Override + public void publishDeviceData(MqttMessage publishData) throws TransportHandlerException { + // nothing to do + } + + @Override + public void processIncomingMessage() { + // nothing to do + } + + @Override + public void processIncomingMessage(MqttMessage message) throws TransportHandlerException { + // nothing to do + } +} diff --git a/modules/samples/connectedcup/component/controller/src/main/java/org/coffeeking/controller/service/util/ConnectedCupServiceUtils.java b/modules/samples/connectedcup/component/controller/src/main/java/org/coffeeking/controller/service/util/ConnectedCupServiceUtils.java new file mode 100644 index 00000000..fd72356c --- /dev/null +++ b/modules/samples/connectedcup/component/controller/src/main/java/org/coffeeking/controller/service/util/ConnectedCupServiceUtils.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.coffeeking.controller.service.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.concurrent.FutureCallback; +import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; +import org.apache.http.impl.nio.client.HttpAsyncClients; +import org.coffeeking.connectedcup.plugin.constants.ConnectedCupConstants; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.device.mgt.analytics.exception.DataPublisherConfigurationException; +import org.wso2.carbon.device.mgt.analytics.service.DeviceAnalyticsService; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import javax.ws.rs.HttpMethod; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.ProtocolException; +import java.net.URL; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; + +public class ConnectedCupServiceUtils { + private static final Log log = LogFactory.getLog(ConnectedCupServiceUtils.class); + + //TODO; replace this tenant domain + private static final String SUPER_TENANT = "carbon.super"; + private static final String TEMPERATURE_STREAM_DEFINITION = "org.wso2.iot.devices.temperature"; + + public static String sendCommandViaHTTP(final String deviceHTTPEndpoint, String urlContext, + boolean fireAndForgot) throws DeviceManagementException { + + String responseMsg = ""; + String urlString = ConnectedCupConstants.URL_PREFIX + deviceHTTPEndpoint + urlContext; + + if (log.isDebugEnabled()) { + log.debug(urlString); + } + + if (!fireAndForgot) { + HttpURLConnection httpConnection = getHttpConnection(urlString); + + try { + httpConnection.setRequestMethod(HttpMethod.GET); + } catch (ProtocolException e) { + String errorMsg = + "Protocol specific error occurred when trying to set method to GET" + + " for:" + urlString; + log.error(errorMsg); + throw new DeviceManagementException(errorMsg, e); + } + + responseMsg = readResponseFromGetRequest(httpConnection); + + } else { + CloseableHttpAsyncClient httpclient = null; + try { + + httpclient = HttpAsyncClients.createDefault(); + httpclient.start(); + HttpGet request = new HttpGet(urlString); + final CountDownLatch latch = new CountDownLatch(1); + Future future = httpclient.execute( + request, new FutureCallback() { + @Override + public void completed(HttpResponse httpResponse) { + latch.countDown(); + } + + @Override + public void failed(Exception e) { + latch.countDown(); + } + + @Override + public void cancelled() { + latch.countDown(); + } + }); + + latch.await(); + + } catch (InterruptedException e) { + if (log.isDebugEnabled()) { + log.debug("Sync Interrupted"); + } + } finally { + try { + if (httpclient != null) { + httpclient.close(); + + } + } catch (IOException e) { + if (log.isDebugEnabled()) { + log.debug("Failed on close"); + } + } + } + } + + return responseMsg; + } + + /* --------------------------------------------------------------------------------------- + Utility methods relevant to creating and sending http requests + --------------------------------------------------------------------------------------- */ + + /* This methods creates and returns a http connection object */ + + public static HttpURLConnection getHttpConnection(String urlString) throws + DeviceManagementException { + + URL connectionUrl = null; + HttpURLConnection httpConnection; + + try { + connectionUrl = new URL(urlString); + httpConnection = (HttpURLConnection) connectionUrl.openConnection(); + } catch (MalformedURLException e) { + String errorMsg = + "Error occured whilst trying to form HTTP-URL from string: " + urlString; + log.error(errorMsg); + throw new DeviceManagementException(errorMsg, e); + } catch (IOException e) { + String errorMsg = "Error occured whilst trying to open a connection to: " + + connectionUrl.toString(); + log.error(errorMsg); + throw new DeviceManagementException(errorMsg, e); + } + + return httpConnection; + } + + /* This methods reads and returns the response from the connection */ + + public static String readResponseFromGetRequest(HttpURLConnection httpConnection) + throws DeviceManagementException { + BufferedReader bufferedReader; + try { + bufferedReader = new BufferedReader(new InputStreamReader( + httpConnection.getInputStream())); + } catch (IOException e) { + String errorMsg = + "There is an issue with connecting the reader to the input stream at: " + + httpConnection.getURL(); + log.error(errorMsg); + throw new DeviceManagementException(errorMsg, e); + } + + String responseLine; + StringBuilder completeResponse = new StringBuilder(); + + try { + while ((responseLine = bufferedReader.readLine()) != null) { + completeResponse.append(responseLine); + } + } catch (IOException e) { + String errorMsg = + "Error occured whilst trying read from the connection stream at: " + + httpConnection.getURL(); + log.error(errorMsg); + throw new DeviceManagementException(errorMsg, e); + } + try { + bufferedReader.close(); + } catch (IOException e) { + log.error( + "Could not succesfully close the bufferedReader to the connection at: " + + httpConnection.getURL()); + } + + return completeResponse.toString(); + } + + public static boolean publishToDAS(String owner, String deviceId, float temperature) { + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext(); + ctx.setTenantDomain(SUPER_TENANT, true); + DeviceAnalyticsService deviceAnalyticsService = (DeviceAnalyticsService) ctx.getOSGiService( + DeviceAnalyticsService.class, null); + Object metdaData[] = {owner, ConnectedCupConstants.DEVICE_TYPE, deviceId, + System.currentTimeMillis()}; + Object payloadData[] = {temperature}; + + try { + deviceAnalyticsService.publishEvent(TEMPERATURE_STREAM_DEFINITION, "1.0.0", metdaData, + new Object[0], payloadData); + } catch (DataPublisherConfigurationException e) { + return false; + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + return true; + } +} diff --git a/modules/samples/connectedcup/component/controller/src/main/webapp/META-INF/webapp-classloading.xml b/modules/samples/connectedcup/component/controller/src/main/webapp/META-INF/webapp-classloading.xml new file mode 100644 index 00000000..ac70a8ca --- /dev/null +++ b/modules/samples/connectedcup/component/controller/src/main/webapp/META-INF/webapp-classloading.xml @@ -0,0 +1,34 @@ + + + + + + + + false + + + CXF,Carbon + diff --git a/modules/samples/connectedcup/component/controller/src/main/webapp/WEB-INF/cxf-servlet.xml b/modules/samples/connectedcup/component/controller/src/main/webapp/WEB-INF/cxf-servlet.xml new file mode 100644 index 00000000..dfbbf91a --- /dev/null +++ b/modules/samples/connectedcup/component/controller/src/main/webapp/WEB-INF/cxf-servlet.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/samples/connectedcup/component/controller/src/main/webapp/WEB-INF/web.xml b/modules/samples/connectedcup/component/controller/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..be105f99 --- /dev/null +++ b/modules/samples/connectedcup/component/controller/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,69 @@ + + + + ConnectedCup-Webapp + + JAX-WS/JAX-RS Endpoint + JAX-WS/JAX-RS Servlet + CXFServlet + + org.apache.cxf.transport.servlet.CXFServlet + + 1 + + + CXFServlet + /* + + + + isAdminService + false + + + doAuthentication + false + + + + + managed-api-enabled + true + + + managed-api-owner + admin + + + managed-api-context-template + /connectedcup/{version} + + + managed-api-application + connectedcup + + + managed-api-isSecured + true + + + diff --git a/modules/samples/connectedcup/component/manager/pom.xml b/modules/samples/connectedcup/component/manager/pom.xml new file mode 100644 index 00000000..7916f097 --- /dev/null +++ b/modules/samples/connectedcup/component/manager/pom.xml @@ -0,0 +1,261 @@ + + + + + + device-mgt-iot-connectedcup + org.wso2.carbon.devicemgt-plugins + 1.9.2-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.coffeeking.manager.service + 1.9.2-SNAPSHOT + war + WSO2 IoTS(Device Types) - Connected Cup Manager Service + WSO2 IoTS(Device Types) - Connected Cup Manager Service + http://wso2.org + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.common + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.core + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.device.mgt.analytics + + + org.apache.axis2.wso2 + axis2-client + + + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.certificate.mgt.core + + + commons-codec.wso2 + commons-codec + + + + + + + + org.apache.cxf + cxf-rt-frontend-jaxws + + + org.apache.cxf + cxf-rt-frontend-jaxrs + + + org.apache.cxf + cxf-rt-transports-http + + + + + org.eclipse.paho + mqtt-client + + + + + org.apache.httpcomponents + httpasyncclient + + + org.wso2.carbon.devicemgt-plugins + org.wso2.carbon.device.mgt.iot + + + + + + org.codehaus.jackson + jackson-core-asl + + + org.codehaus.jackson + jackson-jaxrs + + + javax + javaee-web-api + + + javax.ws.rs + jsr311-api + + + commons-httpclient.wso2 + commons-httpclient + + + + org.wso2.carbon + org.wso2.carbon.utils + + + org.bouncycastle.wso2 + bcprov-jdk15on + + + org.wso2.carbon + org.wso2.carbon.user.api + + + org.wso2.carbon + org.wso2.carbon.queuing + + + org.wso2.carbon + org.wso2.carbon.base + + + org.apache.axis2.wso2 + axis2 + + + org.igniterealtime.smack.wso2 + smack + + + org.igniterealtime.smack.wso2 + smackx + + + jaxen + jaxen + + + commons-fileupload.wso2 + commons-fileupload + + + org.apache.ant.wso2 + ant + + + org.apache.ant.wso2 + ant + + + commons-httpclient.wso2 + commons-httpclient + + + org.eclipse.equinox + javax.servlet + + + org.wso2.carbon + org.wso2.carbon.registry.api + + + + + + commons-codec + commons-codec + + + + org.igniterealtime.smack.wso2 + smack + + + org.igniterealtime.smack.wso2 + smackx + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.annotations + + + + org.wso2.carbon.devicemgt + org.wso2.carbon.apimgt.webapp.publisher + + + + + org.wso2.carbon.devicemgt-plugins + org.coffeeking.connectedcup.plugin + + + + + + + ${basedir}/src/main/java + + + + + maven-compiler-plugin + + 1.7 + 1.7 + + 2.3.2 + + + maven-war-plugin + + connectedcup + + + + + + \ No newline at end of file diff --git a/modules/samples/connectedcup/component/manager/src/main/java/org/coffeeking/manager/service/ConnectedCupManagerService.java b/modules/samples/connectedcup/component/manager/src/main/java/org/coffeeking/manager/service/ConnectedCupManagerService.java new file mode 100644 index 00000000..3e722ffa --- /dev/null +++ b/modules/samples/connectedcup/component/manager/src/main/java/org/coffeeking/manager/service/ConnectedCupManagerService.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.coffeeking.manager.service; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.coffeeking.connectedcup.plugin.constants.ConnectedCupConstants; +import org.wso2.carbon.apimgt.annotations.api.API; +import org.wso2.carbon.apimgt.annotations.device.DeviceType; +import org.wso2.carbon.apimgt.webapp.publisher.KeyGenerationUtil; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.common.DeviceIdentifier; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.EnrolmentInfo; +import org.wso2.carbon.device.mgt.iot.DeviceManagement; +import org.wso2.carbon.device.mgt.iot.apimgt.AccessTokenInfo; +import org.wso2.carbon.device.mgt.iot.apimgt.TokenClient; +import org.wso2.carbon.device.mgt.iot.exception.AccessTokenException; + +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +@API( name="connectedcup_mgt", version="1.0.0", context="/connectedcup_mgt") +public class ConnectedCupManagerService { + private static Log log = LogFactory.getLog(ConnectedCupManagerService.class); + private static final String SUPER_TENANT = "carbon.super"; + + @Context + private HttpServletResponse response; + /** + * @param name + * @param owner + * @return + */ + @Path("cup/register") + @POST + public boolean register(@QueryParam("name") String name, @QueryParam("owner") String owner) { + + + DeviceManagement deviceManagement = new DeviceManagement(SUPER_TENANT); + String deviceId = shortUUID(); + + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(ConnectedCupConstants.DEVICE_TYPE); + + try { + if (deviceManagement.getDeviceManagementService().isEnrolled(deviceIdentifier)) { + response.setStatus(Response.Status.CONFLICT.getStatusCode()); + return false; + } + + Device device = new Device(); + device.setDeviceIdentifier(deviceId); + EnrolmentInfo enrolmentInfo = new EnrolmentInfo(); + enrolmentInfo.setDateOfEnrolment(new Date().getTime()); + enrolmentInfo.setDateOfLastUpdate(new Date().getTime()); + enrolmentInfo.setStatus(EnrolmentInfo.Status.ACTIVE); + enrolmentInfo.setOwnership(EnrolmentInfo.OwnerShip.BYOD); + device.setName(name); + device.setType(ConnectedCupConstants.DEVICE_TYPE); + enrolmentInfo.setOwner(owner); + device.setEnrolmentInfo(enrolmentInfo); + + KeyGenerationUtil.createApplicationKeys(ConnectedCupConstants.DEVICE_TYPE); + + TokenClient accessTokenClient = new TokenClient(ConnectedCupConstants.DEVICE_TYPE); + AccessTokenInfo accessTokenInfo = accessTokenClient.getAccessToken(owner, deviceId); + + //create token + String accessToken = accessTokenInfo.getAccess_token(); + String refreshToken = accessTokenInfo.getRefresh_token(); + List properties = new ArrayList<>(); + + Device.Property accessTokenProperty = new Device.Property(); + accessTokenProperty.setName("accessToken"); + accessTokenProperty.setValue(accessToken); + + Device.Property refreshTokenProperty = new Device.Property(); + refreshTokenProperty.setName("refreshToken"); + refreshTokenProperty.setValue(refreshToken); + + properties.add(accessTokenProperty); + properties.add(refreshTokenProperty); + device.setProperties(properties); + + boolean added = deviceManagement.getDeviceManagementService().enrollDevice(device); + if (added) { + response.setStatus(Response.Status.OK.getStatusCode()); + } else { + response.setStatus(Response.Status.NOT_ACCEPTABLE.getStatusCode()); + } + + return added; + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } catch (AccessTokenException e) { + e.printStackTrace(); + } finally { + deviceManagement.endTenantFlow(); + } + return true; + + } + + @Path("/device/remove/{device_id}") + @DELETE + public void removeDevice(@PathParam("device_id") String deviceId, + @Context HttpServletResponse response) { + + DeviceManagement deviceManagement = new DeviceManagement(SUPER_TENANT); + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(ConnectedCupConstants.DEVICE_TYPE); + + try { + boolean removed = deviceManagement.getDeviceManagementService().disenrollDevice( + deviceIdentifier); + if (removed) { + response.setStatus(Response.Status.OK.getStatusCode()); + + } else { + response.setStatus(Response.Status.NOT_ACCEPTABLE.getStatusCode()); + + } + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } finally { + deviceManagement.endTenantFlow(); + } + + + } + + @Path("/device/update/{device_id}") + @POST + public boolean updateDevice(@PathParam("device_id") String deviceId, + @QueryParam("name") String name, + @Context HttpServletResponse response) { + + DeviceManagement deviceManagement = new DeviceManagement(SUPER_TENANT); + + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(ConnectedCupConstants.DEVICE_TYPE); + + try { + Device device = deviceManagement.getDeviceManagementService().getDevice( + deviceIdentifier); + device.setDeviceIdentifier(deviceId); + + // device.setDeviceTypeId(deviceTypeId); + device.getEnrolmentInfo().setDateOfLastUpdate(new Date().getTime()); + + device.setName(name); + device.setType(ConnectedCupConstants.DEVICE_TYPE); + + boolean updated = deviceManagement.getDeviceManagementService().modifyEnrollment( + device); + + + if (updated) { + response.setStatus(Response.Status.OK.getStatusCode()); + + } else { + response.setStatus(Response.Status.NOT_ACCEPTABLE.getStatusCode()); + + } + return updated; + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return false; + } finally { + deviceManagement.endTenantFlow(); + } + + } + + @Path("/device/{device_id}") + @GET + @Consumes("application/json") + @Produces("application/json") + public Device getDevice(@PathParam("device_id") String deviceId) { + + DeviceManagement deviceManagement = new DeviceManagement(SUPER_TENANT); + DeviceIdentifier deviceIdentifier = new DeviceIdentifier(); + deviceIdentifier.setId(deviceId); + deviceIdentifier.setType(ConnectedCupConstants.DEVICE_TYPE); + + try { + Device device = deviceManagement.getDeviceManagementService().getDevice( + deviceIdentifier); + + return device; + } catch (DeviceManagementException e) { + response.setStatus(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + return null; + } finally { + deviceManagement.endTenantFlow(); + } + + } + + private static String shortUUID() { + UUID uuid = UUID.randomUUID(); + long l = ByteBuffer.wrap(uuid.toString().getBytes(StandardCharsets.UTF_8)).getLong(); + return Long.toString(l, Character.MAX_RADIX); + } + +} \ No newline at end of file diff --git a/modules/samples/connectedcup/component/manager/src/main/webapp/META-INF/webapp-classloading.xml b/modules/samples/connectedcup/component/manager/src/main/webapp/META-INF/webapp-classloading.xml new file mode 100644 index 00000000..ac70a8ca --- /dev/null +++ b/modules/samples/connectedcup/component/manager/src/main/webapp/META-INF/webapp-classloading.xml @@ -0,0 +1,34 @@ + + + + + + + + false + + + CXF,Carbon + diff --git a/modules/samples/connectedcup/component/manager/src/main/webapp/WEB-INF/cxf-servlet.xml b/modules/samples/connectedcup/component/manager/src/main/webapp/WEB-INF/cxf-servlet.xml new file mode 100644 index 00000000..afbc4afc --- /dev/null +++ b/modules/samples/connectedcup/component/manager/src/main/webapp/WEB-INF/cxf-servlet.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/modules/samples/connectedcup/component/manager/src/main/webapp/WEB-INF/web.xml b/modules/samples/connectedcup/component/manager/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..f197abfa --- /dev/null +++ b/modules/samples/connectedcup/component/manager/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,69 @@ + + + + ConnectedCup-Webapp + + JAX-WS/JAX-RS Endpoint + JAX-WS/JAX-RS Servlet + CXFServlet + + org.apache.cxf.transport.servlet.CXFServlet + + 1 + + + CXFServlet + /* + + + + isAdminService + false + + + doAuthentication + false + + + + + managed-api-enabled + false + + + managed-api-owner + admin + + + managed-api-context-template + /connectedcup/{version} + + + managed-api-application + connectedcup + + + managed-api-isSecured + true + + + diff --git a/modules/samples/connectedcup/component/plugin/src/main/java/org/coffeeking/connectedcup/plugin/constants/ConnectedCupConstants.java b/modules/samples/connectedcup/component/plugin/src/main/java/org/coffeeking/connectedcup/plugin/constants/ConnectedCupConstants.java new file mode 100644 index 00000000..e472d3ab --- /dev/null +++ b/modules/samples/connectedcup/component/plugin/src/main/java/org/coffeeking/connectedcup/plugin/constants/ConnectedCupConstants.java @@ -0,0 +1,22 @@ +package org.coffeeking.connectedcup.plugin.constants; + +/** + * Created by menaka on 1/11/16. + */ +public class ConnectedCupConstants { + public final static String DEVICE_TYPE = "connectedcup"; + public final static String DEVICE_PLUGIN_DEVICE_NAME = "DEVICE_NAME"; + public final static String DEVICE_PLUGIN_DEVICE_ID = "CONNECTED_CUP_DEVICE_ID"; + public final static String DEVICE_PLUGIN_PROPERTY_ACCESS_TOKEN = "accessToken"; + public final static String DEVICE_PLUGIN_PROPERTY_REFRESH_TOKEN = "refreshToken"; + public final static String ORDER_ON = "YES"; + public final static String ORDER_OFF = "NO"; + + public static final String URL_PREFIX = "http://"; + public static final String LEVEL_CONTEXT = "/LEVEL/"; + public static final String TEMPERATURE_CONTEXT = "/TEMPERATURE/"; + + public static final String SENSOR_TEMPERATURE = "temperature"; + public static final String SENSOR_LEVEL = "level"; + +} diff --git a/modules/samples/connectedcup/component/plugin/src/main/java/org/coffeeking/connectedcup/plugin/impl/dao/ConnectedCupDAO.java b/modules/samples/connectedcup/component/plugin/src/main/java/org/coffeeking/connectedcup/plugin/impl/dao/ConnectedCupDAO.java new file mode 100644 index 00000000..a4d782dc --- /dev/null +++ b/modules/samples/connectedcup/component/plugin/src/main/java/org/coffeeking/connectedcup/plugin/impl/dao/ConnectedCupDAO.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.coffeeking.connectedcup.plugin.impl.dao; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.coffeeking.connectedcup.plugin.constants.ConnectedCupConstants; +import org.coffeeking.connectedcup.plugin.impl.dao.impl.ConnectedCupDAOImpl; +import org.wso2.carbon.device.mgt.iot.util.iotdevice.dao.IotDeviceDAO; +import org.wso2.carbon.device.mgt.iot.util.iotdevice.dao.IotDeviceManagementDAOException; +import org.wso2.carbon.device.mgt.iot.util.iotdevice.dao.IotDeviceManagementDAOFactory; +import org.wso2.carbon.device.mgt.iot.util.iotdevice.dao.IotDeviceManagementDAOFactoryInterface; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +public class ConnectedCupDAO extends IotDeviceManagementDAOFactory + implements IotDeviceManagementDAOFactoryInterface { + + private static final Log log = LogFactory.getLog(ConnectedCupDAO.class); + static DataSource dataSource; + private static ThreadLocal currentConnection = new ThreadLocal(); + + public ConnectedCupDAO() { + initFireAlarmDAO(); + } + + public static void initFireAlarmDAO() { + dataSource = getDataSourceMap().get(ConnectedCupConstants.DEVICE_TYPE); + } + + @Override public IotDeviceDAO getIotDeviceDAO() { + return new ConnectedCupDAOImpl(); + } + + public static void beginTransaction() throws IotDeviceManagementDAOException { + try { + Connection conn = dataSource.getConnection(); + conn.setAutoCommit(false); + currentConnection.set(conn); + } catch (SQLException e) { + throw new IotDeviceManagementDAOException("Error occurred while retrieving datasource connection", e); + } + } + + public static Connection getConnection() throws IotDeviceManagementDAOException { + if (currentConnection.get() == null) { + try { + currentConnection.set(dataSource.getConnection()); + } catch (SQLException e) { + throw new IotDeviceManagementDAOException("Error occurred while retrieving data source connection", e); + } + } + return currentConnection.get(); + } + + public static void commitTransaction() throws IotDeviceManagementDAOException { + try { + Connection conn = currentConnection.get(); + if (conn != null) { + conn.commit(); + } else { + if (log.isDebugEnabled()) { + log.debug("Datasource connection associated with the current thread is null, hence commit " + + "has not been attempted"); + } + } + } catch (SQLException e) { + throw new IotDeviceManagementDAOException("Error occurred while committing the transaction", e); + } finally { + closeConnection(); + } + } + + public static void closeConnection() throws IotDeviceManagementDAOException { + + Connection con = currentConnection.get(); + if (con != null) { + try { + con.close(); + } catch (SQLException e) { + log.error("Error occurred while close the connection"); + } + } + currentConnection.remove(); + } + + public static void rollbackTransaction() throws IotDeviceManagementDAOException { + try { + Connection conn = currentConnection.get(); + if (conn != null) { + conn.rollback(); + } else { + if (log.isDebugEnabled()) { + log.debug("Datasource connection associated with the current thread is null, hence rollback " + + "has not been attempted"); + } + } + } catch (SQLException e) { + throw new IotDeviceManagementDAOException("Error occurred while rollback the transaction", e); + } finally { + closeConnection(); + } + } +} \ No newline at end of file diff --git a/modules/samples/connectedcup/component/plugin/src/main/java/org/coffeeking/connectedcup/plugin/impl/dao/impl/ConnectedCupDAOImpl.java b/modules/samples/connectedcup/component/plugin/src/main/java/org/coffeeking/connectedcup/plugin/impl/dao/impl/ConnectedCupDAOImpl.java new file mode 100644 index 00000000..3dd17be4 --- /dev/null +++ b/modules/samples/connectedcup/component/plugin/src/main/java/org/coffeeking/connectedcup/plugin/impl/dao/impl/ConnectedCupDAOImpl.java @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.coffeeking.connectedcup.plugin.impl.dao.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.coffeeking.connectedcup.plugin.constants.ConnectedCupConstants; +import org.coffeeking.connectedcup.plugin.impl.dao.ConnectedCupDAO; +import org.wso2.carbon.device.mgt.iot.util.iotdevice.dao.IotDeviceDAO; +import org.wso2.carbon.device.mgt.iot.util.iotdevice.dao.IotDeviceManagementDAOException; +import org.wso2.carbon.device.mgt.iot.util.iotdevice.dao.util.IotDeviceManagementDAOUtil; +import org.wso2.carbon.device.mgt.iot.util.iotdevice.dto.IotDevice; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Implements IotDeviceDAO for virtual firealarm Devices. + */ +public class ConnectedCupDAOImpl implements IotDeviceDAO { + + + private static final Log log = LogFactory.getLog(ConnectedCupDAOImpl.class); + + @Override + public IotDevice getIotDevice(String iotDeviceId) + throws IotDeviceManagementDAOException { + Connection conn = null; + PreparedStatement stmt = null; + IotDevice iotDevice = null; + ResultSet resultSet = null; + try { + conn = ConnectedCupDAO.getConnection(); + String selectDBQuery = + "SELECT CONNECTED_CUP_DEVICE_ID, DEVICE_NAME, ACCESS_TOKEN, REFRESH_TOKEN" + + " FROM CONNECTED_CUP_DEVICE WHERE CONNECTED_CUP_DEVICE_ID = ?"; + stmt = conn.prepareStatement(selectDBQuery); + stmt.setString(1, iotDeviceId); + resultSet = stmt.executeQuery(); + + if (resultSet.next()) { + iotDevice = new IotDevice(); + iotDevice.setIotDeviceName(resultSet.getString( + ConnectedCupConstants.DEVICE_PLUGIN_DEVICE_NAME)); + Map propertyMap = new HashMap(); + propertyMap.put(ConnectedCupConstants.DEVICE_PLUGIN_PROPERTY_ACCESS_TOKEN, resultSet.getString("ACCESS_TOKEN")); + propertyMap.put(ConnectedCupConstants.DEVICE_PLUGIN_PROPERTY_REFRESH_TOKEN, resultSet.getString("REFRESH_TOKEN")); + iotDevice.setDeviceProperties(propertyMap); + + if (log.isDebugEnabled()) { + log.debug("Connected Cup service " + iotDeviceId + " data has been fetched from " + + "Connected Cup database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while fetching Connected Cup device : '" + iotDeviceId + "'"; + log.error(msg, e); + throw new IotDeviceManagementDAOException(msg, e); + } finally { + IotDeviceManagementDAOUtil.cleanupResources(stmt, resultSet); + ConnectedCupDAO.closeConnection(); + } + + return iotDevice; + } + + @Override + public boolean addIotDevice(IotDevice iotDevice) + throws IotDeviceManagementDAOException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = ConnectedCupDAO.getConnection(); + String createDBQuery = + "INSERT INTO CONNECTED_CUP_DEVICE(CONNECTED_CUP_DEVICE_ID, DEVICE_NAME, ACCESS_TOKEN, REFRESH_TOKEN) VALUES (?, ?, ?, ?)"; + + stmt = conn.prepareStatement(createDBQuery); + stmt.setString(1, iotDevice.getIotDeviceId()); + stmt.setString(2, iotDevice.getIotDeviceName()); + stmt.setString(3, iotDevice.getDeviceProperties().get(ConnectedCupConstants.DEVICE_PLUGIN_PROPERTY_ACCESS_TOKEN)); + stmt.setString(4, iotDevice.getDeviceProperties().get(ConnectedCupConstants.DEVICE_PLUGIN_PROPERTY_REFRESH_TOKEN)); + + + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Connected Cup device " + iotDevice.getIotDeviceId() + " data has been" + + " added to the Connected Cup database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while adding the Connected Cup device '" + + iotDevice.getIotDeviceId() + "' to the Connected Cup db."; + log.error(msg, e); + throw new IotDeviceManagementDAOException(msg, e); + } finally { + IotDeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return status; + } + + @Override + public boolean updateIotDevice(IotDevice iotDevice) + throws IotDeviceManagementDAOException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = ConnectedCupDAO.getConnection(); + String updateDBQuery = + "UPDATE CONNECTED_CUP_DEVICE SET DEVICE_NAME = ?, ACCESS_TOKEN=?, REFRESH_TOKEN=? WHERE CONNECTED_CUP_DEVICE_ID = ?"; + + stmt = conn.prepareStatement(updateDBQuery); + + if (iotDevice.getDeviceProperties() == null) { + iotDevice.setDeviceProperties(new HashMap()); + } + stmt.setString(1, iotDevice.getIotDeviceName()); + stmt.setString(2, iotDevice.getDeviceProperties().get( + ConnectedCupConstants.DEVICE_PLUGIN_PROPERTY_ACCESS_TOKEN)); + stmt.setString(3, iotDevice.getDeviceProperties().get( + ConnectedCupConstants.DEVICE_PLUGIN_PROPERTY_ACCESS_TOKEN)); + stmt.setString(4, iotDevice.getIotDeviceId()); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Connected Cup device " + iotDevice.getIotDeviceId() + " data has been" + + " modified."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while modifying the Connected Cup device '" + + iotDevice.getIotDeviceId() + "' data."; + log.error(msg, e); + throw new IotDeviceManagementDAOException(msg, e); + } finally { + IotDeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return status; + } + + @Override + public boolean deleteIotDevice(String iotDeviceId) + throws IotDeviceManagementDAOException { + boolean status = false; + Connection conn = null; + PreparedStatement stmt = null; + try { + conn = ConnectedCupDAO.getConnection(); + String deleteDBQuery = + "DELETE FROM CONNECTED_CUP_DEVICE WHERE CONNECTED_CUP_DEVICE_ID = ?"; + stmt = conn.prepareStatement(deleteDBQuery); + stmt.setString(1, iotDeviceId); + int rows = stmt.executeUpdate(); + if (rows > 0) { + status = true; + if (log.isDebugEnabled()) { + log.debug("Connected Cup device " + iotDeviceId + " data has deleted" + + " from the Connected Cup database."); + } + } + } catch (SQLException e) { + String msg = "Error occurred while deleting Connected Cup device " + iotDeviceId; + log.error(msg, e); + throw new IotDeviceManagementDAOException(msg, e); + } finally { + IotDeviceManagementDAOUtil.cleanupResources(stmt, null); + } + return status; + } + + @Override + public List getAllIotDevices() + throws IotDeviceManagementDAOException { + + Connection conn = null; + PreparedStatement stmt = null; + ResultSet resultSet = null; + IotDevice iotDevice; + List iotDevices = new ArrayList<>(); + + try { + conn = ConnectedCupDAO.getConnection(); + String selectDBQuery = + "SELECT CONNECTED_CUP_DEVICE_ID, DEVICE_NAME, ACCESS_TOKEN, REFRESH_TOKEN" + + "FROM CONNECTED_CUP_DEVICE"; + stmt = conn.prepareStatement(selectDBQuery); + resultSet = stmt.executeQuery(); + while (resultSet.next()) { + iotDevice = new IotDevice(); + iotDevice.setIotDeviceId(resultSet.getString(ConnectedCupConstants.DEVICE_PLUGIN_DEVICE_ID)); + iotDevice.setIotDeviceName(resultSet.getString(ConnectedCupConstants.DEVICE_PLUGIN_DEVICE_NAME)); + + Map propertyMap = new HashMap<>(); + propertyMap.put(ConnectedCupConstants.DEVICE_PLUGIN_PROPERTY_ACCESS_TOKEN, + resultSet.getString("ACCESS_TOKEN")); + propertyMap.put(ConnectedCupConstants.DEVICE_PLUGIN_PROPERTY_REFRESH_TOKEN, + resultSet.getString("REFRESH_TOKEN")); + iotDevice.setDeviceProperties(propertyMap); + iotDevices.add(iotDevice); + } + if (log.isDebugEnabled()) { + log.debug("All Connected Cup device details have fetched from Connected Cup database."); + } + return iotDevices; + } catch (SQLException e) { + String msg = "Error occurred while fetching all Connected Cup device data'"; + log.error(msg, e); + throw new IotDeviceManagementDAOException(msg, e); + } finally { + IotDeviceManagementDAOUtil.cleanupResources(stmt, resultSet); + ConnectedCupDAO.closeConnection(); + } + + } + + } \ No newline at end of file diff --git a/modules/samples/connectedcup/component/plugin/src/main/java/org/coffeeking/connectedcup/plugin/impl/dao/util/ConnectedCupUtils.java b/modules/samples/connectedcup/component/plugin/src/main/java/org/coffeeking/connectedcup/plugin/impl/dao/util/ConnectedCupUtils.java new file mode 100644 index 00000000..a2e298cb --- /dev/null +++ b/modules/samples/connectedcup/component/plugin/src/main/java/org/coffeeking/connectedcup/plugin/impl/dao/util/ConnectedCupUtils.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 org.coffeeking.connectedcup.plugin.impl.dao.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.util.Map; + +/** + * Contains utility methods used by FireAlarm plugin. + */ +public class ConnectedCupUtils { + + private static Log log = LogFactory.getLog(ConnectedCupUtils.class); + + public static String getDeviceProperty(Map deviceProperties, String property) { + + String deviceProperty = deviceProperties.get(property); + + if (deviceProperty == null) { + return ""; + } + + return deviceProperty; + } + + +} diff --git a/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventreceiver_coffeelevel_1.0.0/EventReceiver_coffeelevel.xml b/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventreceiver_coffeelevel_1.0.0/EventReceiver_coffeelevel.xml new file mode 100644 index 00000000..8e4a875a --- /dev/null +++ b/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventreceiver_coffeelevel_1.0.0/EventReceiver_coffeelevel.xml @@ -0,0 +1,9 @@ + + + + false + + + + diff --git a/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventreceiver_coffeelevel_1.0.0/artifact.xml b/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventreceiver_coffeelevel_1.0.0/artifact.xml new file mode 100644 index 00000000..3ba63806 --- /dev/null +++ b/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventreceiver_coffeelevel_1.0.0/artifact.xml @@ -0,0 +1,4 @@ + + + Eventreceiver_coffeelevel.xml + diff --git a/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventstore_coffeelevel_1.0.0/artifact.xml b/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventstore_coffeelevel_1.0.0/artifact.xml new file mode 100644 index 00000000..7d5beecd --- /dev/null +++ b/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventstore_coffeelevel_1.0.0/artifact.xml @@ -0,0 +1,4 @@ + + + org_wso2_iot_devices_coffeelevel.xml + diff --git a/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventstore_coffeelevel_1.0.0/org_wso2_iot_devices_coffeelevel.xml b/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventstore_coffeelevel_1.0.0/org_wso2_iot_devices_coffeelevel.xml new file mode 100644 index 00000000..580b17a3 --- /dev/null +++ b/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventstore_coffeelevel_1.0.0/org_wso2_iot_devices_coffeelevel.xml @@ -0,0 +1,44 @@ + + + + org.wso2.iot.devices.coffeelevel:1.0.0 + + EVENT_STORE + + + meta_owner + true + true + false + STRING + + + meta_deviceType + true + true + false + STRING + + + meta_deviceId + true + true + false + STRING + + + meta_time + true + true + false + LONG + + + coffeelevel + false + false + false + FLOAT + + + \ No newline at end of file diff --git a/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventstream_coffeelevel_1.0.0/artifact.xml b/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventstream_coffeelevel_1.0.0/artifact.xml new file mode 100644 index 00000000..6cd0244f --- /dev/null +++ b/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventstream_coffeelevel_1.0.0/artifact.xml @@ -0,0 +1,5 @@ + + + org.wso2.iot.devices.coffeelevel_1.0.0.json + + diff --git a/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventstream_coffeelevel_1.0.0/org.wso2.iot.devices.coffeelevel_1.0.0.json b/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventstream_coffeelevel_1.0.0/org.wso2.iot.devices.coffeelevel_1.0.0.json new file mode 100644 index 00000000..f9058d22 --- /dev/null +++ b/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Eventstream_coffeelevel_1.0.0/org.wso2.iot.devices.coffeelevel_1.0.0.json @@ -0,0 +1,20 @@ +{ + "name": "org.wso2.iot.devices.coffeelevel", + "version": "1.0.0", + "nickName": "Coffee Level Data", + "description": "Coffee Level data received from the Device", + "metaData": [ + {"name":"owner","type":"STRING"}, + {"name":"deviceType","type":"STRING"}, + {"name":"deviceId","type":"STRING"}, + {"name":"time","type":"LONG"} + ], + "payloadData": [ + { + "name": "coffeelevel","type": "FLOAT" + } + ] +} + + + diff --git a/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Sparkscripts_1.0.0/CoffeeLevel_Sensor_Script.xml b/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Sparkscripts_1.0.0/CoffeeLevel_Sensor_Script.xml new file mode 100644 index 00000000..cb22dc0a --- /dev/null +++ b/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Sparkscripts_1.0.0/CoffeeLevel_Sensor_Script.xml @@ -0,0 +1,20 @@ + + + IoTServer_Sensor_Script + + 0 * * * * ? + diff --git a/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Sparkscripts_1.0.0/artifact.xml b/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Sparkscripts_1.0.0/artifact.xml new file mode 100644 index 00000000..2ed05598 --- /dev/null +++ b/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/Sparkscripts_1.0.0/artifact.xml @@ -0,0 +1,4 @@ + + + CoffeeLevel_Sensor_Script.xml + diff --git a/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/artifacts.xml b/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/artifacts.xml new file mode 100644 index 00000000..08a03ac0 --- /dev/null +++ b/modules/samples/connectedcup/feature/analytics/CoffeeLevel_Sensor/artifacts.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventreceiver_temperature_1.0.0/EventReceiver_temperature.xml b/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventreceiver_temperature_1.0.0/EventReceiver_temperature.xml new file mode 100644 index 00000000..6786696c --- /dev/null +++ b/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventreceiver_temperature_1.0.0/EventReceiver_temperature.xml @@ -0,0 +1,8 @@ + + + + false + + + + diff --git a/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventreceiver_temperature_1.0.0/artifact.xml b/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventreceiver_temperature_1.0.0/artifact.xml new file mode 100644 index 00000000..b72e1ff7 --- /dev/null +++ b/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventreceiver_temperature_1.0.0/artifact.xml @@ -0,0 +1,4 @@ + + + EventReceiver_temperature.xml + diff --git a/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventstore_temperature_1.0.0/artifact.xml b/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventstore_temperature_1.0.0/artifact.xml new file mode 100644 index 00000000..90b14c11 --- /dev/null +++ b/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventstore_temperature_1.0.0/artifact.xml @@ -0,0 +1,4 @@ + + + org_wso2_iot_devices_temperature.xml + diff --git a/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventstore_temperature_1.0.0/org_wso2_iot_devices_temperature.xml b/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventstore_temperature_1.0.0/org_wso2_iot_devices_temperature.xml new file mode 100644 index 00000000..13a625bd --- /dev/null +++ b/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventstore_temperature_1.0.0/org_wso2_iot_devices_temperature.xml @@ -0,0 +1,44 @@ + + + + org.wso2.iot.devices.temperature:1.0.0 + + EVENT_STORE + + + meta_owner + true + true + false + STRING + + + meta_deviceType + true + true + false + STRING + + + meta_deviceId + true + true + false + STRING + + + meta_time + true + true + false + LONG + + + temperature + false + false + false + FLOAT + + + \ No newline at end of file diff --git a/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventstream_temperature_1.0.0/artifact.xml b/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventstream_temperature_1.0.0/artifact.xml new file mode 100644 index 00000000..61e7903b --- /dev/null +++ b/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventstream_temperature_1.0.0/artifact.xml @@ -0,0 +1,5 @@ + + + org.wso2.iot.devices.temperature_1.0.0.json + + diff --git a/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventstream_temperature_1.0.0/org.wso2.iot.devices.temperature_1.0.0.json b/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventstream_temperature_1.0.0/org.wso2.iot.devices.temperature_1.0.0.json new file mode 100644 index 00000000..5d94b982 --- /dev/null +++ b/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Eventstream_temperature_1.0.0/org.wso2.iot.devices.temperature_1.0.0.json @@ -0,0 +1,20 @@ +{ + "name": "org.wso2.iot.devices.temperature", + "version": "1.0.0", + "nickName": "Temperature Data", + "description": "Temperature data received from the Device", + "metaData": [ + {"name":"owner","type":"STRING"}, + {"name":"deviceType","type":"STRING"}, + {"name":"deviceId","type":"STRING"}, + {"name":"time","type":"LONG"} + ], + "payloadData": [ + { + "name": "temperature","type": "FLOAT" + } + ] +} + + + diff --git a/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Sparkscripts_1.0.0/Temperature_Sensor_Script.xml b/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Sparkscripts_1.0.0/Temperature_Sensor_Script.xml new file mode 100644 index 00000000..ee6a7195 --- /dev/null +++ b/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Sparkscripts_1.0.0/Temperature_Sensor_Script.xml @@ -0,0 +1,13 @@ + + + IoTServer_Sensor_Script + + 0 * * * * ? + diff --git a/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Sparkscripts_1.0.0/artifact.xml b/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Sparkscripts_1.0.0/artifact.xml new file mode 100644 index 00000000..58c2dcc1 --- /dev/null +++ b/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/Sparkscripts_1.0.0/artifact.xml @@ -0,0 +1,4 @@ + + + Temperature_Sensor_Script.xml + diff --git a/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/artifacts.xml b/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/artifacts.xml new file mode 100644 index 00000000..b5350763 --- /dev/null +++ b/modules/samples/connectedcup/feature/analytics/Temperature_Sensor/artifacts.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/modules/samples/connectedcup/feature/analytics/build.xml b/modules/samples/connectedcup/feature/analytics/build.xml new file mode 100644 index 00000000..df25af53 --- /dev/null +++ b/modules/samples/connectedcup/feature/analytics/build.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/samples/connectedcup/feature/analytics/pom.xml b/modules/samples/connectedcup/feature/analytics/pom.xml new file mode 100644 index 00000000..b60fafe2 --- /dev/null +++ b/modules/samples/connectedcup/feature/analytics/pom.xml @@ -0,0 +1,72 @@ + + + + + + org.wso2.carbon.devicemgt-plugins + connected-cup-feature-parent + 1.9.2-SNAPSHOT + ../pom.xml + + + + 4.0.0 + org.coffeeking.connectedcup.analytics + pom + 1.9.2-SNAPSHOT + WSO2 IoTS(Device Types) - Connected Cup Analytics + WSO2 IoTS(Device Types) - Connected Cup Analytics + http://wso2.org + + + + + maven-clean-plugin + 2.4.1 + + + auto-clean + initialize + + clean + + + + + + maven-antrun-plugin + 1.7 + + + process-resources + + + + + + + run + + + + + + + + \ No newline at end of file diff --git a/modules/samples/connectedcup/feature/connectedcup-feature/pom.xml b/modules/samples/connectedcup/feature/connectedcup-feature/pom.xml new file mode 100644 index 00000000..11d28a06 --- /dev/null +++ b/modules/samples/connectedcup/feature/connectedcup-feature/pom.xml @@ -0,0 +1,248 @@ + + + + + + org.wso2.carbon.devicemgt-plugins + connected-cup-feature-parent + 1.9.2-SNAPSHOT + ../pom.xml + + + 4.0.0 + org.coffeeking.connectedcup.feature + pom + 1.9.2-SNAPSHOT + WSO2 IoTS(Device Types) - Connected Cup Feature + WSO2 IoTS(Device Types) - Connected Cup Feature + http://wso2.org + + + + org.wso2.carbon.devicemgt-plugins + org.coffeeking.connectedcup.plugin + 1.9.2-SNAPSHOT + + + + org.wso2.carbon.devicemgt-plugins + org.coffeeking.manager.service + 1.9.2-SNAPSHOT + war + + + + org.wso2.carbon.devicemgt-plugins + org.coffeeking.controller.service + 1.9.2-SNAPSHOT + war + + + + org.wso2.carbon.devicemgt-plugins + org.coffeking.agent + 1.9.2-SNAPSHOT + war + + + + com.h2database.wso2 + h2-database-engine + 1.2.140.wso2v3 + + + + + + + + maven-resources-plugin + + + copy-resources + generate-resources + + copy-resources + + + src/main/resources + + + resources + + build.properties + p2.inf + + + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-jaxrs-manager-war + package + + copy + + + + + org.wso2.carbon.devicemgt-plugins + org.coffeeking.manager.service + war + true + ${basedir}/src/main/resources/webapps/ + connectedcup_mgt.war + + + + + + + copy-jaxrs-controller-war + package + + copy + + + + + org.wso2.carbon.devicemgt-plugins + org.coffeeking.controller.service + war + true + ${basedir}/src/main/resources/webapps/ + connectedcup.war + + + + + + + copy-jaxrs-agent-war + package + + copy + + + + + org.wso2.carbon.devicemgt-plugins + org.coffeking.agent + war + true + ${basedir}/src/main/resources/webapps/ + connected-cup-agent.war + + + + + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + + create-connected-cup-plugin-mgt-schema + package + + run + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.wso2.maven + carbon-p2-plugin + + + p2-feature-generation + package + + p2-feature-gen + + + org.coffeeking.connectedcup + ../../../features/etc/feature.properties + + + org.wso2.carbon.p2.category.type:server + org.eclipse.equinox.p2.type.group:false + + + + + org.wso2.carbon.devicemgt-plugins:org.coffeeking.connectedcup.plugin:${carbon.iot.device.mgt.version} + + + + org.wso2.carbon.core.server:${carbon.kernel.version} + + org.wso2.carbon.device.mgt.server:${carbon.device.mgt.version} + + + + + + + + + + diff --git a/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/agent/deviceConfig.properties b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/agent/deviceConfig.properties new file mode 100644 index 00000000..a189416d --- /dev/null +++ b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/agent/deviceConfig.properties @@ -0,0 +1,29 @@ +# +# Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +# +# Licensed 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. +# +# + +#[Device-Configurations] +owner=${DEVICE_OWNER} +deviceId=${DEVICE_ID} +device-name=${DEVICE_NAME} +controller-context=/digital_display/controller +mqtt-ep=${MQTT_EP} +auth-method=token +auth-token=${DEVICE_TOKEN} +refresh-token=${DEVICE_REFRESH_TOKEN} +push-interval=15 + + diff --git a/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/agent/sketch.properties b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/agent/sketch.properties new file mode 100644 index 00000000..e794c194 --- /dev/null +++ b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/agent/sketch.properties @@ -0,0 +1,2 @@ +templates=deviceConfig.properties +zipfilename=DigitalDisplayAgent.zip diff --git a/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/agent/start-device.sh b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/agent/start-device.sh new file mode 100755 index 00000000..e9dd87c4 --- /dev/null +++ b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/agent/start-device.sh @@ -0,0 +1,190 @@ +#!/bin/bash + +echo "----------------------------------------------------------------" +echo "| WSO2 IOT Sample " +echo "| Virtual RaspiAlarm " +echo "| ---------------- " +echo "| ....initializing startup-script " +echo "----------------------------------------------------------------" + +#while true; do +# read -p "What is the network-interface of your device that the Agent should use (find from ifconfig. ex: wlan0,en0,eth0..) > " interface +# +# echo "Setting the network-interface to " $interface +# sed s/^network-interface=.*/network-interface=$interface/ deviceConfig.properties > myTmp +# mv -f myTmp deviceConfig.properties +# break; +#done +# +#while true; do +# read -p "Whats the time-interval (in seconds) between successive Data-Pushes to the WSO2-IoT-Server (ex: '60' indicates 1 minute) > " interval +# +# if [ $interval -eq $interval 2>/dev/null ] +# then +# echo "Setting data-push interval to " $interval " seconds." +# sed s/^push-interval=.*/push-interval=$interval/ deviceConfig.properties > myTmp +# mv -f myTmp deviceConfig.properties +# break; +# else +# echo "Input needs to be an integer indicating the number seconds between successive data-pushes." +# fi +#done + + +#java -jar wso2-firealarm-virtual-agent-advanced.jar + +#while true; do +# read -p "Do you wish to run 'apt-get update' and continue? [Yes/No] " yn +# case $yn in +# [Yy]* ) sudo apt-get update; +# break;; +# [Nn]* ) echo "Continuing without apt-get update..."; +# break;; +# * ) echo "Please answer yes or no."; +# esac +#done +# +#if [ $? -ne 0 ]; then +# echo "apt-get update failed.... Some dependencies may not get installed" +# echo "If an already installed version of the package exists, try running:" +# echo "----------------------------------------------------------------" +# echo "sudo -i" +# echo "cd /var/lib/dpkg/info" +# echo "rm -rf wso2-raspi-alarm*" +# echo "dpkg --remove --force-remove-reinstreq wso2-raspi-alarm" +# echo "exit" +# echo "----------------------------------------------------------------" +# echo "Retry Installation...." +# break; +#fi +# +#echo "Installing 'gdebi' package..." +#sudo apt-get install gdebi # installation of gdebi +# +# +#if [ $? -ne 0 ]; then +# echo "gdebi installation failed.... dependencies will not be installed without gdebi" +# read -p "Do you wish to continue without gdebi? [Yes/No] " yn +# case $yn in +# [Yy]* ) echo "Continueing without gdebi.....";; +# [Nn]* ) echo "Try to resolve errors and re-run the script."; +# exit;; +# * ) exit;; +# esac +#fi +# +# +#for f in ./wso2-raspi-alarm_1.0_armhf.deb; do +# ## Check if the glob gets expanded to existing files. +# ## If not, f here will be exactly the pattern above +# ## and the exists test will evaluate to false. +# # [ -e "$f" ] && echo "'wso2-raspi-alarm_1.0_armhf.deb' file found and installing" || echo "'wso2-raspi-alarm_1.0_armhf.deb' file does not exist in current path"; exit; +# if [ -e "$f" ]; then +# echo "'wso2-raspi-alarm_1.0_armhf.deb' file found and installing now...." +# else +# echo "'wso2-raspi-alarm_1.0_armhf.deb' file does not exist in current path. \nExiting installation..."; +# exit; +# fi +# ## This is all we needed to know, so we can break after the first iteration +# break +#done +# +#echo "Installing the 'wso2-raspi-alarm deb package'" +#sudo gdebi wso2-raspi-alarm_1.0_armhf.deb +# +#if [ $? -ne 0 ]; then +# echo "Installation Failed...." +# exit; +#fi + +#sudo killall -9 python +# +#for f in ./RaspberryAgent.zip; do +# ## Check if the glob gets expanded to existing files. +# ## If not, f here will be exactly the pattern above +# ## and the exists test will evaluate to false. +# # [ -e "$f" ] && echo "'wso2-raspi-alarm_1.0_armhf.deb' file found and installing" || echo "'wso2-raspi-alarm_1.0_armhf.deb' file does not exist in current path"; exit; +# if [ -e "$f" ]; then +# echo "Agent files found......" +# sudo rm -rf /usr/local/src/RaspberryAgent +# sudo unzip RaspberryAgent.zip -d /usr/local/src/ +# else +# echo "'RaspberryAgent.zip' file does not exist in current path. \nInstalling without upgrading agent..."; +# fi +# ## This is all we needed to know, so we can break after the first iteration +# break +#done +# +#for f in /usr/local/src/RaspberryAgent/rc.local; do +# ## Check if the glob gets expanded to existing files. +# ## If not, f here will be exactly the pattern above +# ## and the exists test will evaluate to false. +# if [ -e "$f" ]; then +# echo "Copying boot script" +# sudo mv /usr/local/src/RaspberryAgent/rc.local /etc/rc.local +# sudo chmod +x /etc/rc.local +# else +# echo "Unable to set agent statup on boot"; +# fi +# ## This is all we needed to know, so we can break after the first iteration +# break +#done +# +#for f in ./deviceConfigs.cfg; do +# ## Check if the glob gets expanded to existing files. +# ## If not, f here will be exactly the pattern above +# ## and the exists test will evaluate to false. +# if [ -e "$f" ]; then +# echo "Configuration file found......" +# else +# echo "'deviceConfigs.cfg' file does not exist in current path. \nExiting installation..."; +# exit; +# fi +# ## This is all we needed to know, so we can break after the first iteration +# break +#done +# +#echo "Altering Configuration file" +#sed -i 's|[/,]||g' deviceConfigs.cfg +# +#echo "Copying configurations file to /usr/local/src/RaspberryAgent" +#sudo cp ./deviceConfigs.cfg /usr/local/src/RaspberryAgent/ +# +#if [ $? -ne 0 ]; then +# echo "Copying configuration file failed...." +# exit; +#fi +# +#while true; do +# read -p "Whats the time-interval (in seconds) between successive Data-Pushes to the WSO2-DC (ex: '60' indicates 1 minute) > " input +# +# if [ $input -eq $input 2>/dev/null ] +# then +# echo "Setting data-push interval to $input seconds." +# echo $input > /usr/local/src/RaspberryAgent/time-interval +# break; +# else +# echo "Input needs to be an integer indicating the number seconds between successive data-pushes." +# fi +#done +# +#cd /usr/local/src/RaspberryAgent/ +#sudo chmod +x RaspberryStats.py +#sudo nohup ./RaspberryStats.py -i $input w#kB|x3iPE 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 literal 0 HcmV?d00001 diff --git a/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/carbonapps/Temperature_Sensor.car b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/carbonapps/Temperature_Sensor.car new file mode 100644 index 0000000000000000000000000000000000000000..31eda7ba1d28d258792b58b503ad4a860a0cc251 GIT binary patch literal 4138 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 + + + + + jdbc/ConnectedCupDM_DB + + + diff --git a/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/database/ConnectedCupDM_DB.h2.db b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/database/ConnectedCupDM_DB.h2.db new file mode 100644 index 0000000000000000000000000000000000000000..3f7f3a4eacd974b92b922746398272c64f4e40e4 GIT binary patch literal 20480 zcmeI$y>8P`6ae6qCUKiKR1p<)U^p#pY!jzk$9B_>j(yu$ah%|vwn0c%nxBCYF)$#I z7&~-e;~C%yU}Rz92|!E?m0;ir;3iEG7%XckKhLdPyhu`00mIs5({X%Ki(gdzxeSIw}GXj01BW03ZMWAoIwEp z|IdJhp(ua?D1ZVea76^-vBeixq!nxf1yBG5Pyhu`;FJQXCqFxpzu0Fc9*K={-zT}x z3GUN0_b$o3J!KLMLje>(0Te(16gW2lX+9&AD(mYKkt9kokP-VwB_hdEPEy2qVX41H zi2y>nf3(3O0D$iw_jk;uo_6Y;Jh!4TF$$mn3ZMWApumL_NZo38YMQuq;VQ#|Pyhu` z00mG019fy$Gx89UQTh3lJWhS`2AFD|3)mBjXu5^y*@Yo z{Jbi`yeKeg0$Er}?vClUPhIf!f=!_s6l}{_HY$!o4WF8!5mZByZdwKf-86yy;Q6j@ zIsTE*-b$!K)wRmHTZ0m<34?NkmMMbem~^|py|*J|J3>{0`DAFgRQFjWe^L#Mg^JT( zA9SV@PoDuuei`eX#L$f*HUHhakYFz_JI(*4!a9OY-8G83E2x?XPQ?c&ux+Wk<3KKS z^fEn+k_i#Pr0aTM9}U$FgL+=*S4z}5jMZ4^@r*8Acd1t#5|{xs9-D?RILq+SQjv|eak|mcBcmOBt|27|Y`m|AjrW9%2jd#(^B+c>5P4Wg zKI`=#cp2#wV|kTJD;n!8`?@s zZsh7pt4$i3qG+;AnhIOhR+?>Xr=_-<^|iX1*YcXOv)WP=xtVLWcy{BKEhi1>ZIAkJ zP(xSa1_MmQUFg!LAF?2w8-*+YPq^uk(1|`8)jtoqHzqtOpe26=hXj zfb_#oWbcv=yAP7kuT?3Kc}Ulf{$^K + + + + org.wso2.carbon.ndatasource.rdbms.RDBMSDataSourceReader + + + + ConnectedCupDM_DB + The datasource used for the Connected Cup database + + jdbc/ConnectedCupDM_DB + + + + jdbc:h2:repository/database/ConnectedCupDM_DB;DB_CLOSE_ON_EXIT=FALSE + + wso2carbon + wso2carbon + org.h2.Driver + 50 + 60000 + true + SELECT 1 + 30000 + + + + + diff --git a/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/dbscripts/h2.sql b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/dbscripts/h2.sql new file mode 100644 index 00000000..0d188547 --- /dev/null +++ b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/dbscripts/h2.sql @@ -0,0 +1,13 @@ + +-- ----------------------------------------------------- +-- Table `CONNECTED_CUP_DEVICE` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `CONNECTED_CUP_DEVICE` ( + `CONNECTED_CUP_DEVICE_ID` VARCHAR(45) NOT NULL , + `DEVICE_NAME` VARCHAR(100) NULL DEFAULT NULL, + `ACCESS_TOKEN` VARCHAR(50) NOT NULL, + `REFRESH_TOKEN` VARCHAR(50) NOT NULL, + PRIMARY KEY (`CONNECTED_CUP_DEVICE_ID`) ); + + + diff --git a/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/dbscripts/mysql.sql b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/dbscripts/mysql.sql new file mode 100644 index 00000000..ca39ff82 --- /dev/null +++ b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/dbscripts/mysql.sql @@ -0,0 +1,14 @@ +-- ----------------------------------------------------- +-- Table `CONNECTED_CUP_DEVICE` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `CONNECTED_CUP_DEVICE` ( + `CONNECTED_CUP_DEVICE_ID` VARCHAR(45) NOT NULL , + `DEVICE_NAME` VARCHAR(100) NULL DEFAULT NULL, + `ACCESS_TOKEN` VARCHAR(50) NOT NULL, + `REFRESH_TOKEN` VARCHAR(50) NOT NULL, + PRIMARY KEY (`CONNECTED_CUP_DEVICE_ID`) ) +ENGINE = InnoDB; + + + + diff --git a/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.device-view/device-view.hbs b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.device-view/device-view.hbs new file mode 100644 index 00000000..ee2213f7 --- /dev/null +++ b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.device-view/device-view.hbs @@ -0,0 +1,100 @@ +{{#zone "topCss"}} + +{{/zone}} + +{{#zone "device-thumbnail"}} + +{{/zone}} + +{{#zone "device-opetations"}} +
+ Operations +
+ +
+
+ +
+
+{{/zone}} + +{{#zone "device-detail-properties"}} +
+ +
+
+ +
+
Device Statistics
+ {{unit "iot.unit.device.stats" device=device}} +
+ +
+
Policy Compliance
+
+ +
+
+ Not available yet +
+
+
+
+
+
+
Operations Log
+
+ +
+
+ Not available yet +
+
+
+
+
+
+
+
+{{/zone}} \ No newline at end of file diff --git a/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.device-view/device-view.js b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.device-view/device-view.js new file mode 100644 index 00000000..23d67125 --- /dev/null +++ b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.device-view/device-view.js @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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. + */ + + +function onRequest(context) { + var log = new Log("detail.js"); + var deviceType = context.uriParams.deviceType; + var deviceId = request.getParameter("id"); + + if (deviceType != null && deviceType != undefined && deviceId != null && deviceId != undefined) { + var deviceModule = require("/app/modules/device.js").deviceModule; + var device = deviceModule.viewDevice(deviceType, deviceId); + + if (device && device.status != "error") { + log.info(device); + var deviceProperties = device.properties; + device.accessToken = deviceProperties.accessToken + return {"device": device}; + } + } +} \ No newline at end of file diff --git a/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.device-view/device-view.json b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.device-view/device-view.json new file mode 100644 index 00000000..9eecd8f5 --- /dev/null +++ b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.device-view/device-view.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.device-view/public/images/thumb.png b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.device-view/public/images/thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..7dda85e4c299a7be0663a27e6b0bc7cd3cf99115 GIT binary patch literal 5417 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 T4l&yC00000NkvXXu0mjf$Q68~ literal 0 HcmV?d00001 diff --git a/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/private/conf/device-type.json b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/private/conf/device-type.json new file mode 100644 index 00000000..df273c5e --- /dev/null +++ b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/private/conf/device-type.json @@ -0,0 +1,7 @@ +{ + "deviceType": { + "label": "Connected Cup", + "category": "iot" + } +} + diff --git a/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/public/images/coffeecup.png b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/public/images/coffeecup.png new file mode 100644 index 0000000000000000000000000000000000000000..7dda85e4c299a7be0663a27e6b0bc7cd3cf99115 GIT binary patch literal 5417 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 T4l&yC00000NkvXXu0mjf$Q68~ literal 0 HcmV?d00001 diff --git a/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/public/images/schematicsGuide.png b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/public/images/schematicsGuide.png new file mode 100644 index 0000000000000000000000000000000000000000..44387da55901b5b8ee5df47a1808567f7aa77416 GIT binary patch literal 107251 zcmbSybyQo?*X0Yr-HQY%7OZ$F5+Ib~?i4Q$#l2{N7K#*yQc58>6n7}a9g1s#wm@)q zm*Mw)vu4(szh>sy>*(s~8@x8O zw6eCbwX=8d@bvQb@qOnP_Wnb7#K*{}#H8eu)X!;O(sOe2@(T)!ioaLa)YjEEG&cR{ z?CS36?du;HoS2-No|&EdJ-@tyT3uV;*xcGaIzBl)JHNQRy8aI?5CHyfSdX9o4ebBG z^*9M2G<0+@I@W)1fzW&&-(V7SjHmpVq|%yLZ`{b31VXUMUnXQ%b>J`yY8_HoxR2vf zvIs4+9{mT}e@4$w)~Ka_HQQP7B#za+TPD`X}p7eT>%r#)nCD=#X=Y_Tv{H?wJHT zi-&p&Y{|3dbzI5pAl3Mr z_3epeHR}o7TmbEK_^rGAmuk0WfH0@JnX?PMD`RQtw2*S267rd|4AIo%sAwUMBoFI{ ztspE-S91X1d>GWk?ZL&PZtT?qvhYlnTL|d_^|GTalJ;;?6JwahhE<(iYbdOF(S8(x z9wQ52ia$wR3Hb!rS=>V@8hjumx0|4tU%3aEqYQ?`MEWRZw?5+u|ByF^e3EjwitA~l zA~K_9?jCT0Pv>pu8JVF7R^U*2XD=xopc6aE3h>Z1Cu9cp2DZ{aBMD#`q(-g$69^iG zNFR(YhUR;zWjL__7sGjT<`g-8wo*t)FFU3X(>`#z?E%Lgis%O{{~|1eWZo>g6YWT| zN<_9KBk}ziCXg7%l{jvbXGjdqkV$Z52^!}6#FuAKm{$=y+FJeoMw5gcDM=E(R7BWp zi-Ca9rAdsHLM4i$@Q;JdQiDo|~?PN?Ep0Y)^t z|I)oD$ks;W51ja;PvhvDF`fK3Ox`ivW-rv{_H_7lfhBMh4t;m1yrU;Fd%KyO7}E|- z7Z&S#io|(RM2#W`@+0qQa3IeHxDwsq#x-=6&e+IQ1EV*Mgnw(XS7Fvm=P9Pw6R^Ry z9ZQCueThw*$PAi(IOihCm?c$dxH33C&ly#rW{38R41t{3gPjtH*NSDLc6wBsTiPzD-8{I1SHe91Bsbi?%Lw7HHB3r zvUJ=MXjfS-YR!!Wl^AW&(RWMa=LUc{2Q9-WRJUUuDq(gaCC%8EfxVxbh%rny%@|K0 z2H&<&==F!Oe`yWOOws&{$@|jn7os`8cvN)u`WK%({;-v}d)$S&gN&2HdhKfH`u5%3 z#l3}g+f(wbjbmHd*Q5EM;Va6vuTu~b*t_zsvN-(4<1cxb-!alQ4#{SuTwE}$P7jEa zzG}cH8a~cadS5zd`PX)#3u23;sKU224(N(W8lz_?;T&8ACDh(LPOe2VUl$?&EIEK; zrfe61#$1qtn11Z_qi4P!1J4|u(g}7TaVVFX`PQ&eSvzoid+JdUEaW#n4m{@iYNo8NLcCR3u|K8Hl zr;rMnkh1}!C)dERw?WfbvI-j|2R?bE_4g`D+}VNJ>_H0sfai07zZ~crPFe)%Y2cs{ zwIupo6oCTvVvG)g8cRU*iJdLULW&3=5WI!d&sq8(Ttb~!$TKnXgGL;W z0Axv}aoD~?yN46q)hPuA3FW+zk4!r`n9PnAPK8RxfxHfLLtu&YfM2F7Q&It3cj(4BW^Zd#4IOk32iisD*$vs7S{J8-js` ze*nfIp$eGB5|1A(L}6Rk)-MLa*F%*_jH*};r6K+r7$29!{mhAG5LlX{R9JOD9PFs? zL+gF6%~$UeZU-q%O^eBa(HIBsj)bB9ivu~X1_s7sE0hyOypfU6-SzMrJKgoR9uhQ6eGV5|!ni&vz8tPen z!KpLto<7;FM(T8ghLBG41%7VL~9Ae;wZJ^8H4C;v9g9h)ywAV;HU?-4zm# z|596x&S~lN#TbK_|)E-yH5|?3@_N^}*KM;5ZKec5h||o(@zLiPiAIb4Ek9 z8g$xg2l}+E%0K2@Z>FvUCb=x3>*o(qGX9@eKFQzQ@I zo$EU_UDJn0gJA!NO0BNmC+>s8dI_}Px?h)9-lK7azV}?@nDb#*T?%{&Iu=mQ>y3k4(eQoiUvRVC5@uQkMj zx750Ru$U1!aJN0HSvuW&41TG+INQ!r)1aiq6II3^JVw5H`-E0tiCzyC)wrzA3y*|4 zZTpM7akhX~f)+@)cC0G_7k_~~G`wQDg9P&&Gn&HP1gh9v(?Y6F#wwWREd>->3|oy$V%+8PE+7meSS=GPKs^|+jH!p;?E$!r`&_nT36 zA0!=MQanaJ?Kf$>ioD%Y_K(_7FSHN;OR5JG2kp!1(C0Xgb6Ohn4(-a?Nz=qYNcPf! zOy*Gk8*R8c?VD=QLahB9j*`3EL}_+xqZG#~{usEO`eFx;tv21uKO%|*rLni8m=BSd z%?TN3u6Ae!WBqV&(Sg$HVl%|dhsEt$n_{S{m$rbY4$ZL{@{Sqijg&2#iVfR=lQFqX zZIF}x#*b{Y6+*Mo%d?Ed;&IEwDlT?^1`#qNdgkn<-^`fjF1$<(!yVbot*koCf6oYV zj-c!BG4!!$Ds;(N$nNZp@;l`0N~@gV&vC-WInNt2A)mo`T}TV>a#P?@i}Hx&-?umB zF;HkXCfSR6<0%q@CKkwEX+okR;{>LL#%U`pyGBt$#pzn3?2WDSLRG+PQgrn+JX~{? zOv(6HE)PHe!{PUJs^94EEeC^dmQLhf#ADYVlk$^inMB-jI-@T`c&{5!cS$|^eAxZ_ zjdP2TyUE8sI6t2P@l7a#j!~36UpdD&+>mvs)Lz_Vdfq<2N>yw?4`VF$8^vZ^lw7_O z)%>jYHvC>R-tB6VqL-)69J$|8+kVy*cWcKUYDT)nm&h{hc_wl3_tj$@iPIo@iFp+C zzAUV4>+RlMHueMXyNIIu0eIgGx9)nIE=OcpBt3dB1vjD4@BK^Fpv&)Ds1RZ#a(rfh zmf_XT8!MTNpCAeKbpu9Y0$@w!w*y5BcmIqdu{QL$Vm_~oey~`(eZ3ru8NB-JKeyhj z9P}}xfiXBz4Wo$gU*%Gdl#u|LU#^-bD$DhxAOYBr65o?9w{Ht>bf>R2CR2qB^qE~g zwGon+sG^2(^`PKiQ{*vm&M7ZsnjrreB&p>;JdX6F3+3&Kv665wrs?^F-8g8h4F~Zn zQ{wOjtvQ?r2X$yDMAOVQaCESY^Reb~;U#-;Gc&_#@+n2yWt*M)d*Z~Z%QgiYDlCh^ ze}iKB(l!bYKy5^4S}#_N=kk91NN}>}ez+`jkK-_BDZ}?WCf1MNCJcAEsz~-fK@-6^ zWe>pY&VqXD7oWb_3!Mf$im&IDm!gh&3}kY&WA+#58~1nPgL7Z2)KKSRm;BDb?^aT6 zAOSx-AE$Uz2!+XGWixu?6+qTtVbncoTmMH)pF8vK2L**^F|)1dKLDYhZixxPO17J0{p`O< z#Es5;W@ab5$9;aNnt?uDC!j9tB*H716;wPK@6^;}c@q#o?BQ~r^`te!nTq$?`y1>n z^@yY`E}ZTpsmIniOw(OIw&-F{P^)^jb|Fw+wYg!+k%uhcg|`RyxKiO|%2djmeS&9N zPDyTi#G)wh+-aV~bnl+72>PM;7#LP5&q#5tfnn5m!dgvCxCMXA2!*4#R`*6lmo(7d z(ggHB0oMTnZw&^h;r^NI6oaY-MxyKF%rFgsmfjFs9x2ys#7=B2?z+in*zVdQ1cuMz;9_Jf%(GEo`$UP#PJ z5!$Lpg2IWZ0P(s&;yi^fd4$?6MyE2G~Ru|dm%)?j7DWW%?sNK!Is?iHjJQ6c3GIG zFvS!pUZoqtp`RMUk2{(8E{0tl^*H?o1U6t-@Q%`wt!U38@{kHVP;9A?NG^WiEm1rt zix4?Of||5rNU>xTE2TJt9sey5t;D^*7xi7}G~|uq5sD+)nzb3E6}QWUYjCdCP;e`< zoHS2Tlu~?k%qx#eGdeH5OnV=KGSR1vM(wF~`OOS=+UKy{m9(;^m|d1V_ozKoUCrVx z7cJ3j%?{dpbyp?{4fXb&H8c&L2ln4!GMeIw8|=MJ8cWZkNRH7~^N}1JI>*3;$(Qw{vma#bYSXTC?qu zDDQZh-~Zu+=+d6|qu}?j6#|7_6XaF3?atXnZUXMg(9KKVd#|pye$i;aYSZnzg9ELV z91ii_CsGXejEj*kwtap<0M}2w5)zy_Nt+~RTn)Wh^lEfw8dI;0!>!maSv((rGhAnE zX9?$5K|H?8?eu|1R}BPJd>cDa0YxJtH?j1RN1r;w*0)7R8JH1VQa)Arw;cgKC|2XR zhJ;C<dWz}xIId$J`$2UPD`6}eziM=xTsrOVILl($bLk6tM1&m z{glPqR(|xLm0O=dB*Xo8QO$tv2&1FCiVqQ*;IWKgL*vM>ls>!zs{Y?^1EmoX%3*Oc zv9EY6hFcC|mMyj@E=M6MSZAeQjQi{8vr)PxC3J@}9h$Cyqa7Re}! zUqMffI%4FjG^H1T)^@BpzwtrkMC+meI@I5&fl-F%caVIJ9;PWN!rNfZfa--_spj(u z0SPfpt0$59SY2@pmUgK#&wB-)<47U%a=5yq9)P8bIF}raSv^s+gTMVF+AK(}60u7EpzR`o_qfs}A@USYX)@&mA&qEuoODX?l*tscDw zzK0r$FuZPj0QxvtwVrK5h8;~B?mpe>yLB94#%8YBiawsP+Xx}3AI1xwYwq5HO0rEz zGXo#ln6EVVK3l%BU%2mXeVQbGYf?~rUX%8yfy6ntB~NT{9Qm9(PPO7+D$xa9bW{Gw zQS_lhhSTcDgl_kVTKG9iG>_ws6vj{fY|_j+L=Amac*hd@!sR)_2Hs6n%wTv4yP(h$ zsV^QJ>^m()vrf}bdfDROpq1ob-Zz#-1oK#tBC@BP5i@Q3ZZC|xnqT%|xAIk9Mm-4? z`@rgwS4C{9WpqrFuKe5Fv;3~t5`LU{hm|g4ckGvRf&PJq{v8*FDZ3Z57h;Ycs&4M< zijXR99Ls@rMvpDC?`t(FD%`9RyTP<>5}f27r8<0L8lg#w&uYi^0sO`RDZ|sa)W%M= z$*jT9sL}%E8(G+L9IuCL+Mv}FJkRD2-wN}qA*_!U__|B>j88dYxK}5(ET)&Y@TgIF#fow%kweic^!el@f461?=72N2`*%fPwLoA9mm9DS zSI$`VJBe~&bljvTtgqwnfL_1KjW^0+C@gzUsW=(ru=~p@Jy&7Pw!%)U^`N_|BoC$ z!VXQf%NBj3)ebDKJw2-)<0QWJ=7Y)!ywq~Ra3GK)GoU7OqhTWC8^*Wq-AKPjnXC~@ z`bRXr7K-jN|BbPEa`??p|A{%vfIq)}@5t_Bh`(prwE{QUagcVSM0@4yxDp30N7Oyi zOMn^eP~cjp&RmKzcqujEa`3Ca7AyEO{bhDg(rYiup6r5}kdw13cy^5@fR`yk=A2>8TW z)1iRg>@Y&m!G7J%VXk>9_8& zFLPH^pC~u@)-EWQ8uoFyczu{Q⪚1P5RCDXX0%v=i#}ovAAHg37tLz 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

|2%yv?86Cv<-R*o?N)_tyMt!mnwrALY{$Gvd1p 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%0Ao86pl5OGz~X_}ts~@@7{EWo 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 T4l&yC00000NkvXXu0mjf$Q68~ literal 0 HcmV?d00001 diff --git a/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/public/js/download.js b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/public/js/download.js new file mode 100644 index 00000000..d402f631 --- /dev/null +++ b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/public/js/download.js @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * Licensed 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. + * + * + */ + +var modalPopup = ".wr-modalpopup"; +var modalPopupContainer = modalPopup + " .modalpopup-container"; +var modalPopupContent = modalPopup + " .modalpopup-content"; +var body = "body"; + +/* + * set popup maximum height function. + */ +function setPopupMaxHeight() { + $(modalPopupContent).css('max-height', ($(body).height() - ($(body).height() / 100 * 30))); + $(modalPopupContainer).css('margin-top', (-($(modalPopupContainer).height() / 2))); +} + +/* + * show popup function. + */ +function showPopup() { + $(modalPopup).show(); + setPopupMaxHeight(); + $('#downloadForm').validate({ + rules: { + deviceName: { + minlength: 4, + required: true + } + }, + highlight: function (element) { + $(element).closest('.control-group').removeClass('success').addClass('error'); + }, + success: function (element) { + $(element).closest('.control-group').removeClass('error').addClass('success'); + $('label[for=deviceName]').remove(); + } + }); + var deviceType = ""; + $('.deviceType').each(function () { + if (this.value != "") { + deviceType = this.value; + } + }); + if (deviceType == 'digitaldisplay'){ + $('.sketchType').remove(); + $('input[name="sketchType"][value="digitaldisplay"]').prop('checked', true); + $("label[for='digitaldisplay']").text("Simple Agent"); + }else{ + $('.sketchTypes').remove(); + } +} + +/* + * hide popup function. + */ +function hidePopup() { + $('label[for=deviceName]').remove(); + $('.control-group').removeClass('success').removeClass('error'); + $(modalPopupContent).html(''); + $(modalPopup).hide(); +} + +/* + * DOM ready functions. + */ +$(document).ready(function () { + attachEvents(); +}); + +function attachEvents() { + /** + * Following click function would execute + * when a user clicks on "Download" link + * on Device Management page in WSO2 DC Console. + */ + $("a.download-link").click(function () { + var sketchType = $(this).data("sketchtype"); + var deviceType = $(this).data("devicetype"); + var downloadDeviceAPI = "/devicemgt/api/devices/sketch/generate_link"; + var payload = {"sketchType": sketchType, "deviceType": deviceType}; + $(modalPopupContent).html($('#download-device-modal-content').html()); + showPopup(); + var deviceName; + $("a#download-device-download-link").click(function () { + $('.new-device-name').each(function () { + if (this.value != "") { + deviceName = this.value; + } + }); + $('label[for=deviceName]').remove(); + if (deviceName && deviceName.length >= 4) { + payload.deviceName = deviceName; + invokerUtil.post( + downloadDeviceAPI, + payload, + function (data, textStatus, jqxhr) { + doAction(data); + }, + function (data) { + doAction(data); + } + ); + }else if(deviceName){ + $('.controls').append(''); + $('.control-group').removeClass('success').addClass('error'); + } else { + $('.controls').append(''); + $('.control-group').removeClass('success').addClass('error'); + } + }); + + $("a#download-device-cancel-link").click(function () { + hidePopup(); + }); + + }); +} + +function downloadAgent() { + //$('#downloadForm').submit(); + + var $inputs = $('#downloadForm :input'); + + // not sure if you wanted this, but I thought I'd add it. + // get an associative array of just the values. + var values = {}; + $inputs.each(function() { + values[this.name] = $(this).val(); + }); + + console.log($inputs); + console.log($inputs[0].value); + console.log($inputs[1].value); + + var payload = {} + payload.name = $inputs[0].value; + payload.owner = $inputs[1].value; + invokerUtil.post( + "/connectedcup_mgt/connectedcup/cup/register?name=" + payload.name + "&owner=" + payload.owner, + payload, + function (data, textStatus, jqxhr) { + hidePopup(); + }, + function (data) { + hidePopup(); + } + ); + + var deviceName; + $('.new-device-name').each(function () { + if (this.value != "") { + deviceName = this.value; + } + }); + if (deviceName && deviceName.length >= 4) { + setTimeout(function () { + hidePopup(); + }, 1000); + } +} + +function doAction(data) { + //if it is saml redirection response + if (data.status == null) { + document.write(data); + } + + if (data.status == "200") { + $(modalPopupContent).html($('#download-device-modal-content-links').html()); + $("input#download-device-url").val(data.responseText); + $("input#download-device-url").focus(function () { + $(this).select(); + }); + showPopup(); + } else if (data.status == "401") { + $(modalPopupContent).html($('#device-401-content').html()); + $("#device-401-link").click(function () { + window.location = "/devicemgt/login"; + }); + showPopup(); + } else if (data == "403") { + $(modalPopupContent).html($('#device-403-content').html()); + $("#device-403-link").click(function () { + window.location = "/devicemgt/login"; + }); + showPopup(); + } else { + $(modalPopupContent).html($('#device-unexpected-error-content').html()); + $("a#device-unexpected-error-link").click(function () { + hidePopup(); + }); + } +} \ No newline at end of file diff --git a/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/public/js/jquery.validate.js b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/public/js/jquery.validate.js new file mode 100644 index 00000000..3c1ebb04 --- /dev/null +++ b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/public/js/jquery.validate.js @@ -0,0 +1,1220 @@ +/** + * jQuery Validation Plugin 1.11.0pre + * + * http://bassistance.de/jquery-plugins/jquery-plugin-validation/ + * http://docs.jquery.com/Plugins/Validation + * + * Copyright 2013 Jörn Zaefferer + * Released under the MIT license: + * http://www.opensource.org/licenses/mit-license.php + */ + +(function($) { + +$.extend($.fn, { + // http://docs.jquery.com/Plugins/Validation/validate + validate: function( options ) { + + // if nothing is selected, return nothing; can't chain anyway + if ( !this.length ) { + if ( options && options.debug && window.console ) { + console.warn( "Nothing selected, can't validate, returning nothing." ); + } + return; + } + + // check if a validator for this form was already created + var validator = $.data( this[0], "validator" ); + if ( validator ) { + return validator; + } + + // Add novalidate tag if HTML5. + this.attr( "novalidate", "novalidate" ); + + validator = new $.validator( options, this[0] ); + $.data( this[0], "validator", validator ); + + if ( validator.settings.onsubmit ) { + + this.validateDelegate( ":submit", "click", function( event ) { + if ( validator.settings.submitHandler ) { + validator.submitButton = event.target; + } + // allow suppressing validation by adding a cancel class to the submit button + if ( $(event.target).hasClass("cancel") ) { + validator.cancelSubmit = true; + } + }); + + // validate the form on submit + this.submit( function( event ) { + if ( validator.settings.debug ) { + // prevent form submit to be able to see console output + event.preventDefault(); + } + function handle() { + var hidden; + if ( validator.settings.submitHandler ) { + if ( validator.submitButton ) { + // insert a hidden input as a replacement for the missing submit button + hidden = $("").attr("name", validator.submitButton.name).val(validator.submitButton.value).appendTo(validator.currentForm); + } + validator.settings.submitHandler.call( validator, validator.currentForm, event ); + if ( validator.submitButton ) { + // and clean up afterwards; thanks to no-block-scope, hidden can be referenced + hidden.remove(); + } + return false; + } + return true; + } + + // prevent submit for invalid forms or custom submit handlers + if ( validator.cancelSubmit ) { + validator.cancelSubmit = false; + return handle(); + } + if ( validator.form() ) { + if ( validator.pendingRequest ) { + validator.formSubmitted = true; + return false; + } + return handle(); + } else { + validator.focusInvalid(); + return false; + } + }); + } + + return validator; + }, + // http://docs.jquery.com/Plugins/Validation/valid + valid: function() { + if ( $(this[0]).is("form")) { + return this.validate().form(); + } else { + var valid = true; + var validator = $(this[0].form).validate(); + this.each(function() { + valid &= validator.element(this); + }); + return valid; + } + }, + // attributes: space seperated list of attributes to retrieve and remove + removeAttrs: function( attributes ) { + var result = {}, + $element = this; + $.each(attributes.split(/\s/), function( index, value ) { + result[value] = $element.attr(value); + $element.removeAttr(value); + }); + return result; + }, + // http://docs.jquery.com/Plugins/Validation/rules + rules: function( command, argument ) { + var element = this[0]; + + if ( command ) { + var settings = $.data(element.form, "validator").settings; + var staticRules = settings.rules; + var existingRules = $.validator.staticRules(element); + switch(command) { + case "add": + $.extend(existingRules, $.validator.normalizeRule(argument)); + staticRules[element.name] = existingRules; + if ( argument.messages ) { + settings.messages[element.name] = $.extend( settings.messages[element.name], argument.messages ); + } + break; + case "remove": + if ( !argument ) { + delete staticRules[element.name]; + return existingRules; + } + var filtered = {}; + $.each(argument.split(/\s/), function( index, method ) { + filtered[method] = existingRules[method]; + delete existingRules[method]; + }); + return filtered; + } + } + + var data = $.validator.normalizeRules( + $.extend( + {}, + $.validator.classRules(element), + $.validator.attributeRules(element), + $.validator.dataRules(element), + $.validator.staticRules(element) + ), element); + + // make sure required is at front + if ( data.required ) { + var param = data.required; + delete data.required; + data = $.extend({required: param}, data); + } + + return data; + } +}); + +// Custom selectors +$.extend($.expr[":"], { + // http://docs.jquery.com/Plugins/Validation/blank + blank: function( a ) { return !$.trim("" + a.value); }, + // http://docs.jquery.com/Plugins/Validation/filled + filled: function( a ) { return !!$.trim("" + a.value); }, + // http://docs.jquery.com/Plugins/Validation/unchecked + unchecked: function( a ) { return !a.checked; } +}); + +// constructor for validator +$.validator = function( options, form ) { + this.settings = $.extend( true, {}, $.validator.defaults, options ); + this.currentForm = form; + this.init(); +}; + +$.validator.format = function( source, params ) { + if ( arguments.length === 1 ) { + return function() { + var args = $.makeArray(arguments); + args.unshift(source); + return $.validator.format.apply( this, args ); + }; + } + if ( arguments.length > 2 && params.constructor !== Array ) { + params = $.makeArray(arguments).slice(1); + } + if ( params.constructor !== Array ) { + params = [ params ]; + } + $.each(params, function( i, n ) { + source = source.replace( new RegExp("\\{" + i + "\\}", "g"), function() { + return n; + }); + }); + return source; +}; + +$.extend($.validator, { + + defaults: { + messages: {}, + groups: {}, + rules: {}, + errorClass: "error", + validClass: "valid", + errorElement: "label", + focusInvalid: true, + errorContainer: $([]), + errorLabelContainer: $([]), + onsubmit: true, + ignore: ":hidden", + ignoreTitle: false, + onfocusin: function( element, event ) { + this.lastActive = element; + + // hide error label and remove error class on focus if enabled + if ( this.settings.focusCleanup && !this.blockFocusCleanup ) { + if ( this.settings.unhighlight ) { + this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass ); + } + this.addWrapper(this.errorsFor(element)).hide(); + } + }, + onfocusout: function( element, event ) { + if ( !this.checkable(element) && (element.name in this.submitted || !this.optional(element)) ) { + this.element(element); + } + }, + onkeyup: function( element, event ) { + if ( event.which === 9 && this.elementValue(element) === "" ) { + return; + } else if ( element.name in this.submitted || element === this.lastElement ) { + this.element(element); + } + }, + onclick: function( element, event ) { + // click on selects, radiobuttons and checkboxes + if ( element.name in this.submitted ) { + this.element(element); + } + // or option elements, check parent select in that case + else if ( element.parentNode.name in this.submitted ) { + this.element(element.parentNode); + } + }, + highlight: function( element, errorClass, validClass ) { + if ( element.type === "radio" ) { + this.findByName(element.name).addClass(errorClass).removeClass(validClass); + } else { + $(element).addClass(errorClass).removeClass(validClass); + } + }, + unhighlight: function( element, errorClass, validClass ) { + if ( element.type === "radio" ) { + this.findByName(element.name).removeClass(errorClass).addClass(validClass); + } else { + $(element).removeClass(errorClass).addClass(validClass); + } + } + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/setDefaults + setDefaults: function( settings ) { + $.extend( $.validator.defaults, settings ); + }, + + messages: { + required: "This field is required.", + remote: "Please fix this field.", + email: "Please enter a valid email address.", + url: "Please enter a valid URL.", + date: "Please enter a valid date.", + dateISO: "Please enter a valid date (ISO).", + number: "Please enter a valid number.", + digits: "Please enter only digits.", + creditcard: "Please enter a valid credit card number.", + equalTo: "Please enter the same value again.", + maxlength: $.validator.format("Please enter no more than {0} characters."), + minlength: $.validator.format("Please enter at least {0} characters."), + rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."), + range: $.validator.format("Please enter a value between {0} and {1}."), + max: $.validator.format("Please enter a value less than or equal to {0}."), + min: $.validator.format("Please enter a value greater than or equal to {0}.") + }, + + autoCreateRanges: false, + + prototype: { + + init: function() { + this.labelContainer = $(this.settings.errorLabelContainer); + this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm); + this.containers = $(this.settings.errorContainer).add( this.settings.errorLabelContainer ); + this.submitted = {}; + this.valueCache = {}; + this.pendingRequest = 0; + this.pending = {}; + this.invalid = {}; + this.reset(); + + var groups = (this.groups = {}); + $.each(this.settings.groups, function( key, value ) { + if ( typeof value === "string" ) { + value = value.split(/\s/); + } + $.each(value, function( index, name ) { + groups[name] = key; + }); + }); + var rules = this.settings.rules; + $.each(rules, function( key, value ) { + rules[key] = $.validator.normalizeRule(value); + }); + + function delegate(event) { + var validator = $.data(this[0].form, "validator"), + eventType = "on" + event.type.replace(/^validate/, ""); + if ( validator.settings[eventType] ) { + validator.settings[eventType].call(validator, this[0], event); + } + } + $(this.currentForm) + .validateDelegate(":text, [type='password'], [type='file'], select, textarea, " + + "[type='number'], [type='search'] ,[type='tel'], [type='url'], " + + "[type='email'], [type='datetime'], [type='date'], [type='month'], " + + "[type='week'], [type='time'], [type='datetime-local'], " + + "[type='range'], [type='color'] ", + "focusin focusout keyup", delegate) + .validateDelegate("[type='radio'], [type='checkbox'], select, option", "click", delegate); + + if ( this.settings.invalidHandler ) { + $(this.currentForm).bind("invalid-form.validate", this.settings.invalidHandler); + } + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/form + form: function() { + this.checkForm(); + $.extend(this.submitted, this.errorMap); + this.invalid = $.extend({}, this.errorMap); + if ( !this.valid() ) { + $(this.currentForm).triggerHandler("invalid-form", [this]); + } + this.showErrors(); + return this.valid(); + }, + + checkForm: function() { + this.prepareForm(); + for ( var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++ ) { + this.check( elements[i] ); + } + return this.valid(); + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/element + element: function( element ) { + element = this.validationTargetFor( this.clean( element ) ); + this.lastElement = element; + this.prepareElement( element ); + this.currentElements = $(element); + var result = this.check( element ) !== false; + if ( result ) { + delete this.invalid[element.name]; + } else { + this.invalid[element.name] = true; + } + if ( !this.numberOfInvalids() ) { + // Hide error containers on last error + this.toHide = this.toHide.add( this.containers ); + } + this.showErrors(); + return result; + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/showErrors + showErrors: function( errors ) { + if ( errors ) { + // add items to error list and map + $.extend( this.errorMap, errors ); + this.errorList = []; + for ( var name in errors ) { + this.errorList.push({ + message: errors[name], + element: this.findByName(name)[0] + }); + } + // remove items from success list + this.successList = $.grep( this.successList, function( element ) { + return !(element.name in errors); + }); + } + if ( this.settings.showErrors ) { + this.settings.showErrors.call( this, this.errorMap, this.errorList ); + } else { + this.defaultShowErrors(); + } + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/resetForm + resetForm: function() { + if ( $.fn.resetForm ) { + $(this.currentForm).resetForm(); + } + this.submitted = {}; + this.lastElement = null; + this.prepareForm(); + this.hideErrors(); + this.elements().removeClass( this.settings.errorClass ).removeData( "previousValue" ); + }, + + numberOfInvalids: function() { + return this.objectLength(this.invalid); + }, + + objectLength: function( obj ) { + var count = 0; + for ( var i in obj ) { + count++; + } + return count; + }, + + hideErrors: function() { + this.addWrapper( this.toHide ).hide(); + }, + + valid: function() { + return this.size() === 0; + }, + + size: function() { + return this.errorList.length; + }, + + focusInvalid: function() { + if ( this.settings.focusInvalid ) { + try { + $(this.findLastActive() || this.errorList.length && this.errorList[0].element || []) + .filter(":visible") + .focus() + // manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find + .trigger("focusin"); + } catch(e) { + // ignore IE throwing errors when focusing hidden elements + } + } + }, + + findLastActive: function() { + var lastActive = this.lastActive; + return lastActive && $.grep(this.errorList, function( n ) { + return n.element.name === lastActive.name; + }).length === 1 && lastActive; + }, + + elements: function() { + var validator = this, + rulesCache = {}; + + // select all valid inputs inside the form (no submit or reset buttons) + return $(this.currentForm) + .find("input, select, textarea") + .not(":submit, :reset, :image, [disabled]") + .not( this.settings.ignore ) + .filter(function() { + if ( !this.name ) { + if ( window.console ) { + console.error( "%o has no name assigned", this ); + } + throw new Error( "Failed to validate, found an element with no name assigned. See console for element reference." ); + } + + // select only the first element for each name, and only those with rules specified + if ( this.name in rulesCache || !validator.objectLength($(this).rules()) ) { + return false; + } + + rulesCache[this.name] = true; + return true; + }); + }, + + clean: function( selector ) { + return $(selector)[0]; + }, + + errors: function() { + var errorClass = this.settings.errorClass.replace(" ", "."); + return $(this.settings.errorElement + "." + errorClass, this.errorContext); + }, + + reset: function() { + this.successList = []; + this.errorList = []; + this.errorMap = {}; + this.toShow = $([]); + this.toHide = $([]); + this.currentElements = $([]); + }, + + prepareForm: function() { + this.reset(); + this.toHide = this.errors().add( this.containers ); + }, + + prepareElement: function( element ) { + this.reset(); + this.toHide = this.errorsFor(element); + }, + + elementValue: function( element ) { + var type = $(element).attr("type"), + val = $(element).val(); + + if ( type === "radio" || type === "checkbox" ) { + return $("input[name='" + $(element).attr("name") + "']:checked").val(); + } + + if ( typeof val === "string" ) { + return val.replace(/\r/g, ""); + } + return val; + }, + + check: function( element ) { + element = this.validationTargetFor( this.clean( element ) ); + + var rules = $(element).rules(); + var dependencyMismatch = false; + var val = this.elementValue(element); + var result; + + for (var method in rules ) { + var rule = { method: method, parameters: rules[method] }; + try { + + result = $.validator.methods[method].call( this, val, element, rule.parameters ); + + // if a method indicates that the field is optional and therefore valid, + // don't mark it as valid when there are no other rules + if ( result === "dependency-mismatch" ) { + dependencyMismatch = true; + continue; + } + dependencyMismatch = false; + + if ( result === "pending" ) { + this.toHide = this.toHide.not( this.errorsFor(element) ); + return; + } + + if ( !result ) { + this.formatAndAdd( element, rule ); + return false; + } + } catch(e) { + if ( this.settings.debug && window.console ) { + console.log( "Exception occured when checking element " + element.id + ", check the '" + rule.method + "' method.", e ); + } + throw e; + } + } + if ( dependencyMismatch ) { + return; + } + if ( this.objectLength(rules) ) { + this.successList.push(element); + } + return true; + }, + + // return the custom message for the given element and validation method + // specified in the element's HTML5 data attribute + customDataMessage: function( element, method ) { + return $(element).data("msg-" + method.toLowerCase()) || (element.attributes && $(element).attr("data-msg-" + method.toLowerCase())); + }, + + // return the custom message for the given element name and validation method + customMessage: function( name, method ) { + var m = this.settings.messages[name]; + return m && (m.constructor === String ? m : m[method]); + }, + + // return the first defined argument, allowing empty strings + findDefined: function() { + for(var i = 0; i < arguments.length; i++) { + if ( arguments[i] !== undefined ) { + return arguments[i]; + } + } + return undefined; + }, + + defaultMessage: function( element, method ) { + return this.findDefined( + this.customMessage( element.name, method ), + this.customDataMessage( element, method ), + // title is never undefined, so handle empty string as undefined + !this.settings.ignoreTitle && element.title || undefined, + $.validator.messages[method], + "Warning: No message defined for " + element.name + "" + ); + }, + + formatAndAdd: function( element, rule ) { + var message = this.defaultMessage( element, rule.method ), + theregex = /\$?\{(\d+)\}/g; + if ( typeof message === "function" ) { + message = message.call(this, rule.parameters, element); + } else if (theregex.test(message)) { + message = $.validator.format(message.replace(theregex, "{$1}"), rule.parameters); + } + this.errorList.push({ + message: message, + element: element + }); + + this.errorMap[element.name] = message; + this.submitted[element.name] = message; + }, + + addWrapper: function( toToggle ) { + if ( this.settings.wrapper ) { + toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) ); + } + return toToggle; + }, + + defaultShowErrors: function() { + var i, elements; + for ( i = 0; this.errorList[i]; i++ ) { + var error = this.errorList[i]; + if ( this.settings.highlight ) { + this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass ); + } + this.showLabel( error.element, error.message ); + } + if ( this.errorList.length ) { + this.toShow = this.toShow.add( this.containers ); + } + if ( this.settings.success ) { + for ( i = 0; this.successList[i]; i++ ) { + this.showLabel( this.successList[i] ); + } + } + if ( this.settings.unhighlight ) { + for ( i = 0, elements = this.validElements(); elements[i]; i++ ) { + this.settings.unhighlight.call( this, elements[i], this.settings.errorClass, this.settings.validClass ); + } + } + this.toHide = this.toHide.not( this.toShow ); + this.hideErrors(); + this.addWrapper( this.toShow ).show(); + }, + + validElements: function() { + return this.currentElements.not(this.invalidElements()); + }, + + invalidElements: function() { + return $(this.errorList).map(function() { + return this.element; + }); + }, + + showLabel: function( element, message ) { + var label = this.errorsFor( element ); + if ( label.length ) { + // refresh error/success class + label.removeClass( this.settings.validClass ).addClass( this.settings.errorClass ); + + // check if we have a generated label, replace the message then + if ( label.attr("generated") ) { + label.html(message); + } + } else { + // create label + label = $("<" + this.settings.errorElement + "/>") + .attr({"for": this.idOrName(element), generated: true}) + .addClass(this.settings.errorClass) + .html(message || ""); + if ( this.settings.wrapper ) { + // make sure the element is visible, even in IE + // actually showing the wrapped element is handled elsewhere + label = label.hide().show().wrap("<" + this.settings.wrapper + "/>").parent(); + } + if ( !this.labelContainer.append(label).length ) { + if ( this.settings.errorPlacement ) { + this.settings.errorPlacement(label, $(element) ); + } else { + label.insertAfter(element); + } + } + } + if ( !message && this.settings.success ) { + label.text(""); + if ( typeof this.settings.success === "string" ) { + label.addClass( this.settings.success ); + } else { + this.settings.success( label, element ); + } + } + this.toShow = this.toShow.add(label); + }, + + errorsFor: function( element ) { + var name = this.idOrName(element); + return this.errors().filter(function() { + return $(this).attr("for") === name; + }); + }, + + idOrName: function( element ) { + return this.groups[element.name] || (this.checkable(element) ? element.name : element.id || element.name); + }, + + validationTargetFor: function( element ) { + // if radio/checkbox, validate first element in group instead + if ( this.checkable(element) ) { + element = this.findByName( element.name ).not(this.settings.ignore)[0]; + } + return element; + }, + + checkable: function( element ) { + return (/radio|checkbox/i).test(element.type); + }, + + findByName: function( name ) { + return $(this.currentForm).find("[name='" + name + "']"); + }, + + getLength: function( value, element ) { + switch( element.nodeName.toLowerCase() ) { + case "select": + return $("option:selected", element).length; + case "input": + if ( this.checkable( element) ) { + return this.findByName(element.name).filter(":checked").length; + } + } + return value.length; + }, + + depend: function( param, element ) { + return this.dependTypes[typeof param] ? this.dependTypes[typeof param](param, element) : true; + }, + + dependTypes: { + "boolean": function( param, element ) { + return param; + }, + "string": function( param, element ) { + return !!$(param, element.form).length; + }, + "function": function( param, element ) { + return param(element); + } + }, + + optional: function( element ) { + var val = this.elementValue(element); + return !$.validator.methods.required.call(this, val, element) && "dependency-mismatch"; + }, + + startRequest: function( element ) { + if ( !this.pending[element.name] ) { + this.pendingRequest++; + this.pending[element.name] = true; + } + }, + + stopRequest: function( element, valid ) { + this.pendingRequest--; + // sometimes synchronization fails, make sure pendingRequest is never < 0 + if ( this.pendingRequest < 0 ) { + this.pendingRequest = 0; + } + delete this.pending[element.name]; + if ( valid && this.pendingRequest === 0 && this.formSubmitted && this.form() ) { + $(this.currentForm).submit(); + this.formSubmitted = false; + } else if (!valid && this.pendingRequest === 0 && this.formSubmitted) { + $(this.currentForm).triggerHandler("invalid-form", [this]); + this.formSubmitted = false; + } + }, + + previousValue: function( element ) { + return $.data(element, "previousValue") || $.data(element, "previousValue", { + old: null, + valid: true, + message: this.defaultMessage( element, "remote" ) + }); + } + + }, + + classRuleSettings: { + required: {required: true}, + email: {email: true}, + url: {url: true}, + date: {date: true}, + dateISO: {dateISO: true}, + number: {number: true}, + digits: {digits: true}, + creditcard: {creditcard: true} + }, + + addClassRules: function( className, rules ) { + if ( className.constructor === String ) { + this.classRuleSettings[className] = rules; + } else { + $.extend(this.classRuleSettings, className); + } + }, + + classRules: function( element ) { + var rules = {}; + var classes = $(element).attr("class"); + if ( classes ) { + $.each(classes.split(" "), function() { + if ( this in $.validator.classRuleSettings ) { + $.extend(rules, $.validator.classRuleSettings[this]); + } + }); + } + return rules; + }, + + attributeRules: function( element ) { + var rules = {}; + var $element = $(element); + + for (var method in $.validator.methods) { + var value; + + // support for in both html5 and older browsers + if ( method === "required" ) { + value = $element.get(0).getAttribute(method); + // Some browsers return an empty string for the required attribute + // and non-HTML5 browsers might have required="" markup + if ( value === "" ) { + value = true; + } + // force non-HTML5 browsers to return bool + value = !!value; + } else { + value = $element.attr(method); + } + + if ( value ) { + rules[method] = value; + } else if ( $element[0].getAttribute("type") === method ) { + rules[method] = true; + } + } + + // maxlength may be returned as -1, 2147483647 (IE) and 524288 (safari) for text inputs + if ( rules.maxlength && /-1|2147483647|524288/.test(rules.maxlength) ) { + delete rules.maxlength; + } + + return rules; + }, + + dataRules: function( element ) { + var method, value, + rules = {}, $element = $(element); + for (method in $.validator.methods) { + value = $element.data("rule-" + method.toLowerCase()); + if ( value !== undefined ) { + rules[method] = value; + } + } + return rules; + }, + + staticRules: function( element ) { + var rules = {}; + var validator = $.data(element.form, "validator"); + if ( validator.settings.rules ) { + rules = $.validator.normalizeRule(validator.settings.rules[element.name]) || {}; + } + return rules; + }, + + normalizeRules: function( rules, element ) { + // handle dependency check + $.each(rules, function( prop, val ) { + // ignore rule when param is explicitly false, eg. required:false + if ( val === false ) { + delete rules[prop]; + return; + } + if ( val.param || val.depends ) { + var keepRule = true; + switch (typeof val.depends) { + case "string": + keepRule = !!$(val.depends, element.form).length; + break; + case "function": + keepRule = val.depends.call(element, element); + break; + } + if ( keepRule ) { + rules[prop] = val.param !== undefined ? val.param : true; + } else { + delete rules[prop]; + } + } + }); + + // evaluate parameters + $.each(rules, function( rule, parameter ) { + rules[rule] = $.isFunction(parameter) ? parameter(element) : parameter; + }); + + // clean number parameters + $.each(["minlength", "maxlength", "min", "max"], function() { + if ( rules[this] ) { + rules[this] = Number(rules[this]); + } + }); + $.each(["rangelength", "range"], function() { + var parts; + if ( rules[this] ) { + if ( $.isArray(rules[this]) ) { + rules[this] = [Number(rules[this][0]), Number(rules[this][1])]; + } else if ( typeof rules[this] === "string" ) { + parts = rules[this].split(/[\s,]+/); + rules[this] = [Number(parts[0]), Number(parts[1])]; + } + } + }); + + if ( $.validator.autoCreateRanges ) { + // auto-create ranges + if ( rules.min && rules.max ) { + rules.range = [rules.min, rules.max]; + delete rules.min; + delete rules.max; + } + if ( rules.minlength && rules.maxlength ) { + rules.rangelength = [rules.minlength, rules.maxlength]; + delete rules.minlength; + delete rules.maxlength; + } + } + + return rules; + }, + + // Converts a simple string to a {string: true} rule, e.g., "required" to {required:true} + normalizeRule: function( data ) { + if ( typeof data === "string" ) { + var transformed = {}; + $.each(data.split(/\s/), function() { + transformed[this] = true; + }); + data = transformed; + } + return data; + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/addMethod + addMethod: function( name, method, message ) { + $.validator.methods[name] = method; + $.validator.messages[name] = message !== undefined ? message : $.validator.messages[name]; + if ( method.length < 3 ) { + $.validator.addClassRules(name, $.validator.normalizeRule(name)); + } + }, + + methods: { + + // http://docs.jquery.com/Plugins/Validation/Methods/required + required: function( value, element, param ) { + // check if dependency is met + if ( !this.depend(param, element) ) { + return "dependency-mismatch"; + } + if ( element.nodeName.toLowerCase() === "select" ) { + // could be an array for select-multiple or a string, both are fine this way + var val = $(element).val(); + return val && val.length > 0; + } + if ( this.checkable(element) ) { + return this.getLength(value, element) > 0; + } + return $.trim(value).length > 0; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/remote + remote: function( value, element, param ) { + if ( this.optional(element) ) { + return "dependency-mismatch"; + } + + var previous = this.previousValue(element); + if (!this.settings.messages[element.name] ) { + this.settings.messages[element.name] = {}; + } + previous.originalMessage = this.settings.messages[element.name].remote; + this.settings.messages[element.name].remote = previous.message; + + param = typeof param === "string" && {url:param} || param; + + if ( previous.old === value ) { + return previous.valid; + } + + previous.old = value; + var validator = this; + this.startRequest(element); + var data = {}; + data[element.name] = value; + $.ajax($.extend(true, { + url: param, + mode: "abort", + port: "validate" + element.name, + dataType: "json", + data: data, + success: function( response ) { + validator.settings.messages[element.name].remote = previous.originalMessage; + var valid = response === true || response === "true"; + if ( valid ) { + var submitted = validator.formSubmitted; + validator.prepareElement(element); + validator.formSubmitted = submitted; + validator.successList.push(element); + delete validator.invalid[element.name]; + validator.showErrors(); + } else { + var errors = {}; + var message = response || validator.defaultMessage( element, "remote" ); + errors[element.name] = previous.message = $.isFunction(message) ? message(value) : message; + validator.invalid[element.name] = true; + validator.showErrors(errors); + } + previous.valid = valid; + validator.stopRequest(element, valid); + } + }, param)); + return "pending"; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/minlength + minlength: function( value, element, param ) { + var length = $.isArray( value ) ? value.length : this.getLength($.trim(value), element); + return this.optional(element) || length >= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/maxlength + maxlength: function( value, element, param ) { + var length = $.isArray( value ) ? value.length : this.getLength($.trim(value), element); + return this.optional(element) || length <= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/rangelength + rangelength: function( value, element, param ) { + var length = $.isArray( value ) ? value.length : this.getLength($.trim(value), element); + return this.optional(element) || ( length >= param[0] && length <= param[1] ); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/min + min: function( value, element, param ) { + return this.optional(element) || value >= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/max + max: function( value, element, param ) { + return this.optional(element) || value <= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/range + range: function( value, element, param ) { + return this.optional(element) || ( value >= param[0] && value <= param[1] ); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/email + email: function( value, element ) { + // contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/ + return this.optional(element) || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/url + url: function( value, element ) { + // contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/ + return this.optional(element) || /^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/date + date: function( value, element ) { + return this.optional(element) || !/Invalid|NaN/.test(new Date(value).toString()); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/dateISO + dateISO: function( value, element ) { + return this.optional(element) || /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/number + number: function( value, element ) { + return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/digits + digits: function( value, element ) { + return this.optional(element) || /^\d+$/.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/creditcard + // based on http://en.wikipedia.org/wiki/Luhn + creditcard: function( value, element ) { + if ( this.optional(element) ) { + return "dependency-mismatch"; + } + // accept only spaces, digits and dashes + if ( /[^0-9 \-]+/.test(value) ) { + return false; + } + var nCheck = 0, + nDigit = 0, + bEven = false; + + value = value.replace(/\D/g, ""); + + for (var n = value.length - 1; n >= 0; n--) { + var cDigit = value.charAt(n); + nDigit = parseInt(cDigit, 10); + if ( bEven ) { + if ( (nDigit *= 2) > 9 ) { + nDigit -= 9; + } + } + nCheck += nDigit; + bEven = !bEven; + } + + return (nCheck % 10) === 0; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/equalTo + equalTo: function( value, element, param ) { + // bind to the blur event of the target in order to revalidate whenever the target field is updated + // TODO find a way to bind the event just once, avoiding the unbind-rebind overhead + var target = $(param); + if ( this.settings.onfocusout ) { + target.unbind(".validate-equalTo").bind("blur.validate-equalTo", function() { + $(element).valid(); + }); + } + return value === target.val(); + } + + } + +}); + +// deprecated, use $.validator.format instead +$.format = $.validator.format; + +}(jQuery)); + +// ajax mode: abort +// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]}); +// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort() +(function($) { + var pendingRequests = {}; + // Use a prefilter if available (1.5+) + if ( $.ajaxPrefilter ) { + $.ajaxPrefilter(function( settings, _, xhr ) { + var port = settings.port; + if ( settings.mode === "abort" ) { + if ( pendingRequests[port] ) { + pendingRequests[port].abort(); + } + pendingRequests[port] = xhr; + } + }); + } else { + // Proxy ajax + var ajax = $.ajax; + $.ajax = function( settings ) { + var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode, + port = ( "port" in settings ? settings : $.ajaxSettings ).port; + if ( mode === "abort" ) { + if ( pendingRequests[port] ) { + pendingRequests[port].abort(); + } + return (pendingRequests[port] = ajax.apply(this, arguments)); + } + return ajax.apply(this, arguments); + }; + } +}(jQuery)); + +// provides delegate(type: String, delegate: Selector, handler: Callback) plugin for easier event delegation +// handler is only called when $(event.target).is(delegate), in the scope of the jquery-object for event.target +(function($) { + $.extend($.fn, { + validateDelegate: function( delegate, type, handler ) { + return this.bind(type, function( event ) { + var target = $(event.target); + if ( target.is(delegate) ) { + return handler.apply(target, arguments); + } + }); + } + }); +}(jQuery)); diff --git a/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/type-view.hbs b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/type-view.hbs new file mode 100644 index 00000000..dbce7a03 --- /dev/null +++ b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/type-view.hbs @@ -0,0 +1,256 @@ +

+
+ +
+
+

Ingredients

+
+

Hardware Requirements

+
+
    + + + + Coffee Cup
    +
+
+ +
View API   + Create an Instance + +
+ +
+ + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +

+
+ +
+

Prepare


+

Get your device ready

+
+
    +

    01 Set up your RaspberryPi device as shown in the schematic below and get the Digital Display setup.

    +

    02 Connect a monitor to your RaspberryPi via the HDMI cable to get a UI view of the device.

    +

    03 Get the RaspberryPi to connect to the internet (via Ethernet or Wifi) and note its IP_ADDRESS

    +
+
+
+ +
+

Schematic Diagram


+

Click on the image to zoom

+
+ + + +
+
+
+ +
+

Connect (Quick Start)


+

Internet of Things Foundation Quickstart connection

+
+
    +

    01 Click on the 'Create DEB' button above to get the download link for the Digital Display setup files

    +

    02 (The following commands can be issued by directly typing into the terminal of the device or by an 'ssh' login from a remote PC)

    +

    03 Download the Digital Display setup files using the following command: 'curl -k < url_link_received_from_the_above_step >> Agent.zip'
    This will download a zip file named 'Agent.zip'

    +
+
+
+ + + + + + + +{{#zone "bottomJs"}} + {{js "/js/download.js"}} + {{js "/js/jquery.validate.js"}} +{{/zone}} + diff --git a/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/type-view.json b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/type-view.json new file mode 100644 index 00000000..9eecd8f5 --- /dev/null +++ b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/jaggeryapps/devicemgt/app/units/cdmf.unit.device.type.connectedcup.type-view/type-view.json @@ -0,0 +1,3 @@ +{ + "version": "1.0.0" +} \ No newline at end of file diff --git a/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/p2.inf b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/p2.inf new file mode 100644 index 00000000..3e428b37 --- /dev/null +++ b/modules/samples/connectedcup/feature/connectedcup-feature/src/main/resources/p2.inf @@ -0,0 +1,16 @@ +instructions.configure = \ +org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../conf/device-types/);\ +org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.coffeeking.connectedcup_${feature.version}/configs/,target:${installFolder}/../../conf/device-types/,overwrite:true);\ +org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../deployment/server/webapps/);\ +org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.coffeeking.connectedcup_${feature.version}/webapps/,target:${installFolder}/../../deployment/server/webapps/,overwrite:true);\ +org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../deployment/server/carbonapps/);\ +org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.coffeeking.connectedcup_${feature.version}/carbonapps/,target:${installFolder}/../../deployment/server/carbonapps/,overwrite:true);\ +org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../resources/sketches/);\ +org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../resources/sketches/connectedcup/);\ +org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.coffeeking.connectedcup_${feature.version}/agent/,target:${installFolder}/../../resources/sketches/connectedcup/,overwrite:true);\ +org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.coffeeking.connectedcup_${feature.version}/dbscripts/,target:${installFolder}/../../../dbscripts/cdm/plugins/connectedcup,overwrite:true);\ +org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.coffeeking.connectedcup_${feature.version}/datasources/,target:${installFolder}/../../conf/datasources/,overwrite:true);\ +org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../deployment/server/jaggeryapps/);\ +org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.coffeeking.connectedcup_${feature.version}/jaggeryapps/,target:${installFolder}/../../deployment/server/jaggeryapps/,overwrite:true);\ +org.eclipse.equinox.p2.touchpoint.natives.mkdir(path:${installFolder}/../../database/);\ +org.eclipse.equinox.p2.touchpoint.natives.copy(source:${installFolder}/../features/org.coffeeking.connectedcup_${feature.version}/database/,target:${installFolder}/../../database/,overwrite:true);\ \ No newline at end of file